Основы встроенного ассемблера для Linux на платформе z Systems

Повышение производительности приложений IBM z Systems с использованием встроенного ассемблера компилятора IBM XL C/C++

Встроенный ассемблер позволяет использовать ассемблерный код в наиболее критичных частях приложений. Благодаря этому разработчики ПО могут использовать все аппаратные преимущества платформы IBM z Systems™ и проявлять свое мастерство при создании высокопроизводительных приложений. В этой статье рассматриваются основы встроенного ассемблера, поддерживаемого компиляторами IBM для платформы Linux on z Systems.

Ан Тьень Тран, разработчик программного обеспечения, IBM

Anh Tuyen Tran's photoАн Тьень Тран (Anh Tuyen Tran) имеет степень бакалавра в области разработки программного обеспечения и информатики университета Торонто (Канада), а также степень магистра финансов университета Ямагути (Япония). Работает разработчиком программного обеспечения в компании IBM с 2007 года. Сначала являлся членом группы по разработке отладчиков, а в настоящее время работает в группе по разработке компиляторов. С 2009 по 2013 гг. работал руководителем группы Test Servers Administration и отвечал за работу с компиляторами. Ан – один из основных авторов документации по встроенному ассемблеру для Linux на платформе z Systems, аппаратной транзакционной памяти и встроенным криптографическим функциям архитектуры IBM POWER8. Написал несколько статей, посвященных компиляторам IBM.



30.03.2016

Введение

Компилятор IBM XL C/C++ для Linux на платформе z Systems версии 1.1, выпущенный в 2015 году, позволяет непосредственно встраивать ассемблерные инструкции (ассемблерный код) в приложения, разрабатываемые на языке C/C++. Благодаря этому опытные разработчики могут создавать более эффективные приложения, используя инструкции на уровне процессора. Встроенный ассемблер позволяет разработчикам ПО использовать ассемблерный код в наиболее критичных частях своих программ на C/C++, раскрывая весь свой потенциал и обеспечивая наилучшее быстродействие своих программ.

Цель данной статьи – познакомить вас с основами встроенного ассемблера, поддерживаемого компилятором IBM XL для Linux на платформе z Systems Более сложные вопросы будут рассматриваться в статье Advanced features of inline assembly for Linux on z Systems (EN). В этой статье будут рассмотрены инструкции ассемблера, использующие основные регистры. Векторные регистры и регистры с плавающей точкой будут рассмотрены отдельно. Статья адресована опытным разработчикам, которые используют компилятор для Linux на платформе z Systems и хотят расширить возможности оптимизации своих высокопроизводительных приложений.


Ассемблерные инструкции и операторы встроенного ассемблера

Операторы встроенного ассемблера являются платформенно-зависимыми

Каждый оператор встроенного ассемблера содержит одну или несколько ассемблерных инструкций, либо не содержит их вообще. Эти инструкции, будучи инструкциями аппаратного уровня, работают с конкретной процессорной архитектурой. Поэтому инструкции для разных платформ могут быть совершенно разными, даже если они выполняют одинаковые действия. Например, инструкция арифметического сложения содержимого двух регистров для архитектуры IBM Power Architecture® использует три операнда: R1, R2 и R3.

caxo. R3, R2, R1

Эта операция складывает значения, хранящиеся в регистрах R1 и R2, и сохраняет результат в регистре R3. Также обновляется информация о статусе операции в регистрах CR (регистр состояния) и XER (регистр исключительной ситуации при операциях с фиксированной точкой).

Однако в системах IBM z Systems соответствующая инструкция использует два операнда.

AR R2, R1

Эта операция складывает значения, хранящиеся в регистрах R1 и R2, и сохраняет результат в регистре R2. Информация о статусе операции и переполнении помещается в слово состояния процессора (PSW) в виде 4-битового кода условия. Регистр состояния в процессорах архитектуры z Systems отсутствует.

Однако отметим, что если в системе архитектуры z Systems инсталлирована функция отдельных операндов, инструкции ARK и AGRK используют три операнда.

Для обеспечения переносимости приложений код, содержащий операторы встроенного ассемблера, необходимо защищать соответствующими специальными макросами архитектуры z Systems. Чтобы получить список всех макросов компилятора IBM XL, откомпилируйте любой фрагмент написанного на C кода с опцией –qshowmacros –P. Вывод предварительной обработки будет содержать определения всех макросов.

Составные элементы операторов встроенного ассемблера

Операторы встроенного ассемблера помещаются в код разрабатываемых на C/C++ приложений, а компилятор внедряет ассемблерные инструкции в генерируемый код. Основными элементами операторов встроенного ассемблера являются:

  • Ключевое слово asm, __asm или __asm__, означающее начало ассемблерного оператора.
  • Необязательное ключевое слово volatile, позволяющее указать компилятору, что вставляемый ассемблерный код может давать побочные эффекты.
  • Одна или несколько ассемблерных инструкций, которые необходимо внедрить в код.
  • Список выходных операндов для отображения вывода ассемблерных инструкций.
  • Список входных операндов, содержащих входные данные ассемблерных инструкций.
  • Необязательный список экранирования (clobber list), сообщающий компилятору о регистрах, кодах условия и областях памяти, задействованных в ассемблерных инструкциях.

На рисунке 1 изображена обобщенная схема построения оператора встроенного ассемблера. Более подробная информация об основных элементах будет представлена далее в этой статье.

Рисунок 1. Обобщенная конструкция оператора встроенного ассемблера
Обобщенная конструкция оператора встроенного ассемблера

Операнды ассемблерных инструкций, входящих в состав операторов встроенного ассемблера

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

Рисунок 2. Комбинированный список операндов
Комбинированный список операндов

Встроенный ассемблер требует, чтобы все обращения в оставшейся части программы осуществлялись через списки входных и выходных операндов. Прямой доступ к внешним символам без прохождения через списки операндов не поддерживается.


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

Работая со встроенным ассемблером, программисты не манипулируют ассемблерными инструкциями напрямую. Вместо этого встроенный ассемблер позволяет применять ассемблерные инструкции к символам из списка операндов, избавляя от необходимости писать дополнительный сопутствующий код. Чтобы облегчить использование ассемблерных инструкций, компилятор выполняет все необходимые подготовительные действия. Например, для сложения значений двух переменных нужно лишь вызвать инструкцию сложения (например, AR) с двумя переменными, служащими в качестве двух операндов. Все остальные сопутствующие задачи (выбор регистров, загрузка в них значений, сохранение результатов в памяти) выполняются компилятором.

В следующих листингах представлены две версии одной и той же программы: без использования встроенного ассемблера (example01.c) и с его использованием (example02.c).

Листинг 1. Модуль example01.c –приложение на C без использования встроенного ассемблера
#include <stdio.h>
int main () {
   int array[] = { 1, 2, 3 };
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   return 0;
}

Строки 4 и 5 в модуле example01.c идентичны и используются в этом примере в качестве маркеров для демонстрации подготовительных действий, выполняемых компилятором при обработке операторов встроенного ассемблера. Модуль example02.c отличается от модуля example01.c наличием оператора встроенного ассемблера между строками 4 и 5. В операторе используется инструкция сложения (AR), которая прибавляет значение массива array[2] к значению массива array[1].

Листинг 2. Модуль example02.c – приложение на C с использованием встроенного ассемблера
#include <stdio.h>
int main () {
   int array[] = { 1, 2, 3 };
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   asm ("AR %0, %1 \n" 
       :"+r"(array[1]) 
       :"r"(array[2]) );
   printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]);
   return 0;
}

Инструкция AR сохраняет сумму операндов в первом операнде: она складывает значения %0 (array[1]) и %1 (array[2], и сохраняет результат в %0 (array[1]). Из листинга 3 видно, что при выполнении программы example02.c выводится содержимое массива, второй элемент которого (array[1]) равен 5.

Листинг 3. Запуск программы example02.c
xlc –o  example02 ./example02.c
./example02  
array = [ 1, 2, 3]
array = [ 1, 5, 3]         <- Второй элемент массива равен 5 (сумма 2+3)

Сравнив ассемблерный код, сгенерированный в обоих случаях, мы сможем увидеть, каким образом компилятор осуществляет поддержку выражений встроенного ассемблера (строки 5-7 в модуле example02.c). Чтобы сгенерировать ассемблерные файлы модулей example01.c и example02.c, скомпилируйте их с опцией –S.

Листинг 4. Компиляция модулей с опцией –S для получения ассемблерных файлов
xlc –c –S example01.c example02.c

На рисунке 3 приведено сравнение ассемблерных файлов, полученных в результате компиляции модулей example01.c и example02.c. Различия в файлах example01.s (слева) и example02.s (справа) очевидны; мы видим, что перед тем как поместить в код инструкцию AR %r0, %r1, компилятор выполнил ряд дополнительных действий – выбрал регистры общего назначения (r0 и r1) и загрузил в них соответствующие значения перед вызовом инструкции AR. Кроме того, после выполнения инструкции AR компилятор сохранил результат в массив. Эти дополнительные действия необходимы для успешного выполнения оператора встроенного ассемблера.

Рисунок 3. Дополнительные операции, выполненные компилятором при обработке инструкции AR
Дополнительные операции, выполненные компилятором при обработке инструкции AR

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

Таблица 1. Операции, выполняемые компилятором при обработке оператора встроенного ассемблера
Код на CКод на ассемблереВыполняемая операция
printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]); BRASL %r14,printf Подготовка завершена, вызов метода printf в строке 4
(инструкции, добавленные компилятором) L %r1,184(,%r15) Загрузка значения элемента array[2] в регистр r1
MVC 168(4,%r15),180(%r15) Копирование значения элемента array[1] в местоположение r15+168
L %r0,168(,%r15) Загрузка значения элемента array[1], находящегося теперь в r15+168, в регистр r0
asm ("AR %0, %1 \n" :"+r"(array[1]) :"r"(array[2]) ); #GS00000 Начало вставки в код ассемблерной инструкции пользователя
AR %r0, %r1 Вставка в код ассемблерной инструкции пользователя
#GE00000 Завершение вставки в код ассемблерной инструкции пользователя
(инструкции, добавленные компилятором) ST %r0,168(,%r15) Сохранение r0 (array[1]) назад в местоположение r15+168
MVC 180(4,%r15),168(%r15) Копирование значения из r15+168 в r15+180 (элемент array[1] обновлен)
printf ( "array = [ %d, %d, %d ]\n", array[0], array[1], array[2]); LA %r2,16(,%r13) Подготовка ко второму вызову метода printf

Ограничители, модификаторы, списки входных и выходных операндов, список экранирования

Каждый выходной операнд состоит из ограничителя и заключенного в скобки выражения C/C++ и должен содержать модификатор. Входные операнды также состоят из ограничителя и заключенного в скобки выражения C/C++, но не содержат модификатора. Список экранирования сообщает компилятору о том, влияют ли ассемблерные инструкции на какие-либо объекты, не перечисленные в списках входных и выходных операндов. Списки всех трех типов могут быть пустыми. Если список содержит несколько элементов, то они разделяются запятыми.

Ограничители

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

Ограничители a, d и r для регистров общего назначения

Ограничители a, d и r применяются к ассемблерным инструкциям, операндом которых является регистр общего назначения. В приложении C/C++ ограничители a, r и d означают целочисленные символы. В модуле example03.c (листинг 5) используется инструкция LPGFR, которая приводит любое значение целочисленной переменной к абсолютному и загружает его в эту же переменную. Поскольку переменная a является целочисленной, а операндами инструкции LPGFR являются регистры общего назначения, использование ограничителей a, d и r совместно с инструкцией LPGFR допустимо.

Листинг 5. Модуль example03.c – использование ограничителей a, d и r для регистров общего назначения
#include<stdio.h>
int abs_a(int a){
    asm (" LPGFR %0, %0\n" :"+a"(a) );        // ограничитель a
    return a;
}
int abs_d(int a){
    asm (" LPGFR %0, %0\n" :"+d"(a) );        // ограничитель d
    return a;
}
int abs_r(int a){
    asm (" LPGFR %0, %0\n" :"+r"(a) );        // ограничитель r
    return a;
}

int main() {
    int x = -5;
    printf( "Absolute value of %d is %d (a constraint)\n", x, abs_a(x) );
    printf( "Absolute value of %d is %d (d constraint)\n", x, abs_d(x) );
    printf( "Absolute value of %d is %d (r constraint)\n", x, abs_r(x) );
    x = 12;
    printf( "Absolute value of %d is %d (a constraint)\n", x, abs_a(x) );
    printf( "Absolute value of %d is %d (d constraint)\n", x, abs_d(x) );
    printf( "Absolute value of %d is %d (r constraint)\n", x, abs_r(x) );
}

Во время выполнения программы example03.c будут выведены абсолютные значения чисел -5 и 12.

Примечание. Архитектура IBM z/Architecture® не допускает использование регистра r0 для адресации. По этой причине ограничитель "a" (сокращение от "address" - адрес) может использоваться для всех регистров общего назначения, за исключением r0.

Ограничители I, J и K для констант длиной до двух байтов

Ограничители I,J и K применяются к ассемблерным инструкциям, работающим с непосредственными операндами. В приложении C/C++ ограничители I,J и K означают целочисленные или символьные константы длиной до 16 битов. В модуле example04.c (листинг 6) все три ограничителя представляют собой однобайтовый символ при использовании инструкции MVI (Move Immediate) и двухбайтовое целое число – при использовании инструкции AHI (Add Half-word Immediate). Использование ограничителей I,J и K допустимо, поскольку инструкция MVI принимает в качестве аргумента однобайтовое непосредственное значение, а инструкция AHI – двухбайтовую целочисленную константу.

Листинг 6. Модуль example04.c – использование ограничителей I, J и K
#include<stdio.h>
int main() {
 char text[]=”ibm”;
 asm(“MVI %0,%1\n”:”=m”(text[0]):”I”(‘I’)); //I является 1-байтовой литерой (тип char)
 asm(“MVI %0,%1\n”:”=m”(text[1]):”J”(‘B’)); //J является 1-байтовой литерой (тип char)
 asm(“MVI %0,%1\n”:”=m”(text[2]):”K”(‘M’)); //K является 1-байтовой литерой (тип char)
 printf (“Expected IBM , got %s\n”, text); 

 int x = 0;
 asm(“AHI %0,%1\n”:”+r”(x):”I”(0x1FFF)); //I является 2-байтовым целым числом (тип int)
 asm(“AHI %0,%1\n”:”+r”(x):”J”(0x1FFF)); //J является 2-байтовым целым числом (тип int)
 asm(“AHI %0,%1\n”:”+r”(x):”K”(0x1FFF)); //K является 2-байтовым целым числом (тип int)
 printf (“Expected 0x5FFD, got 0x%X\n”, x);
 return 0;
}

Во время выполнения программы example04.c будут выведены ожидаемые значения.

Ограничители g, i и n для констант длиной до четырех байтов

Ограничители g, i и n применяются к ассемблерным инструкциям, работающим с непосредственными операндами. В приложении C/C++ ограничители g, i и n означают целочисленные или символьные константы длиной до 32 битов. В модуле example05.c (листинг 7) все три ограничителя представляют собой однобайтовый символ при использовании инструкции MVI, двухбайтовое целое число – при использовании инструкции AHI и четырехбайтовое целое число – при использовании инструкции AFI (Add Full-word Immediate). Использование ограничителей g, i и n допустимо, поскольку инструкция MVI принимает в качестве аргумента однобайтовое непосредственное значение, инструкция AHI – двухбайтовую целочисленную константу, а инструкция AFI – четырехбайтовую целочисленную константу.

Листинг 7. Модуль example05.c – использование ограничителей g, i и n
#include <stdio.h> 
int main() {
 char text[]="xlc";
 asm("MVI %0,%1\n":"=m"(text[0]):"i"('X')); //i является 1-байтовой литерой (тип char)
 asm("MVI %0,%1\n":"=m"(text[1]):"n"('L')); //n является 1-байтовой литерой (тип char)
 asm("MVI %0,%1\n":"=m"(text[2]):"g"('C')); //g является 1-байтовой литерой (тип char)
 printf ("Expected XLC, got %s\n", text);

 int x = 0;
 asm("AHI %0,%1\n":"+r"(x):"i"(0x1FFF)); //i является 2-байтовым целым числом (тип int)
 asm("AHI %0,%1\n":"+r"(x):"n"(0x1FFF)); //n является 2-байтовым целым числом (тип int)
 asm("AHI %0,%1\n":"+r"(x):"g"(0x1FFF)); //g является 2-байтовым целым числом (тип int)
 printf ("Expected 0x5FFD, got 0x%X\n", x);

 x = 0;
 asm("AFI %0,%1\n":"+r"(x):"i"(0x1FFFFFF)); //i является 4-байтовым целым числом (тип int)
 asm("AFI %0,%1\n":"+r"(x):"n"(0x1FFFFFF)); //n является 4-байтовым целым числом (тип int)
 asm("AFI %0,%1\n":"+r"(x):"g"(0x1FFFFFF)); //g является 4-байтовым целым числом (тип int)
 printf ("Expected 0x5FFFFFD, got 0x%X\n", x);
 return 0;
}

Во время выполнения программы example05.c будут выведены ожидаемые значения.

Адресные ограничители Q, g, m и o

Ограничители Q,g,m и o применяются к ассемблерным инструкциям, работающим с адресными операндами вида D(X, B), где D – это смещение, X - индекс, а B – базовый регистр. В приложении C/C++ ограничители Q,g,m и o означают целочисленные символы. В модуле example06.c (листинг 8) все ограничители являются адресными операндами инструкции ST (Store), обновляющей значения элементов массива.

Листинг 8. Модуль example06.c – использование ограничителей Q, g, m и o
#include <stdio.h>
int main () {
 int a[] = { 1, 2, 3, 4 };
 int b[] = { 10, 20, 30, 40 };
 printf ( "a = [ %d, %d, %d, %d ]\n", a[0], a[1], a[2], a[3] );

 asm ("ST %1,%0\n":"=Q"(a[0]):"r"(b[0])); //Q – адресный ограничитель
 asm ("ST %1,%0\n":"=g"(a[1]):"r"(b[1])); //g – адресный ограничитель
 asm ("ST %1,%0\n":"=m"(a[2]):"r"(b[2])); //m – адресный ограничитель
 asm ("ST %1,%0\n":"=o"(a[3]):"r"(b[3])); //o – адресный ограничитель

 printf ( "a = [ %d, %d, %d, %d ]\n", a[0], a[1], a[2], a[3] );
 return 0;
}

Ограничители совпадения 0, 1, 2, … , 9

Ограничители совпадения 0, 1, …, 9 рекомендуют компилятору выделять один и тот же регистр для входного операнда и для нумерованного выходного операнда. Таким образом, ограничители совпадения можно использовать только совместно с входными операндами. Это необходимо, когда в качестве входных данных какой-либо операции используется результат предыдущей операции. Если не задействовать ограничители совпадения, компилятор не узнает о том, что для входного и выходного операндов необходимо использовать один и тот же регистр. Пример использования ограничителей совпадения подробно рассматривается в статье Advanced features of inline assembly for Linux on z Systems (EN).

Модификаторы

Модификаторы сообщают компилятору дополнительную информацию о соответствующих операндах. В последующих версиях компилятора их список может расшириться. Для получения полного списка модификаторов каждого компилятора обратитесь к его документации. В текущей версии компилятора для платформы Linux on z Systems поддерживаются следующие модификаторы.

  • Модификатор "=" означает, что в текущей инструкции операнд используется только для записи. Его предыдущее значение удаляется и заменяется выходными данными.
  • Модификатор "+" означает, что в текущей инструкции операнд используется как для чтения, так и для записи.
  • Модификатор "&" означает, что операнд может быть изменен входными операндами до завершения инструкции.
  • Модификатор "%" объявляет, что инструкция является коммутативной для текущего и следующего за ним операндов. Это означает, что в инструкции можно безопасно менять местами операнд с модификатором "%" и следующий за ним операнд. Соответственно, модификатор "%" нельзя применять к последнему операнду. Модификатор "%" предоставляет компилятору возможность оптимизировать код. Компилятор переставит операнды, если посчитает, что это приведет к улучшению производительности.

Модификаторы "=" (операнд только для записи) и "+" (операнд для записи и чтения) важно использовать правильно. Их неправильное использование может привести к непредсказуемым результатам. Чтобы продемонстрировать это, рассмотрим пример из листинга 9, в котором укажем модификатор "=" там, где следовало указать модификатор "+".

Листинг 9. Модуль example08a.c – неправильное использование модификатора
#include <stdio.h>
int main () {
   int a = 10, b = 200;
   printf ("INITIAL: a = %d, b = %d\n", a, b );        // Для переменной a используется модификатор “=”
   asm ("AR %0, %1\n" 
            :"=r"(a) 
            :"r"(b));
   printf ("RESULT : a = %d, b = %d\n", a, b );
   return 0;
}

Рассмотренная программа должна помещать сумму переменных a и b в переменную a. Для этой цели используется инструкция AR, которая складывает значения двух операндов и сохраняет результат в первом операнде. Соответственно, к первому операнду применяются операции записи и чтения. Применив модификатор "=", мы сообщили компилятору о том, что к первому операнду (переменная a) должна применяться только операция чтения. Это вводит компилятор в заблуждение, и в результате выполнения кода модуля example08a.c мы получим ошибочный результат.

Листинг 10. Ошибочный результат при неправильном использовании модификатора
xlc -o example08a ./example08a.c  
./example08a
INITIAL: a = 10, b = 200
RESULT : a = 200, b = 200        <- переменная a должна равняться 210

При использовании правильного модификатора "+" мы получим корректный результат. На рисунке 4 показаны различия в коде, сгенерированном компилятором при использовании правильного (справа) и неправильного (слева) модификаторов.

Рисунок 4. Различия в ассемблерном коде при использовании различных модификаторов
Различия в ассемблерном коде при использовании различных модификаторов

Эти различия показывают, что использование модификатора "=" (только для записи) действительно вводит компилятор в заблуждение. Из левой части рисунка видно, что компилятор не выполняет операцию загрузки в регистр r0 [ L %r0,172(,%r15) ] перед вызовом инструкции AR [ AR %r0, %r1 ]. В результате при выполнении инструкции AR регистр r0 содержит не значение переменной a, а значение, которое находилось в нем ранее. Это объясняет ошибочный результат при использовании модификатора "=". С другой стороны, как видно из правой части рисунка, использование правильного модификатора "+" не создает такой проблемы. Перед использованием регистра r0 компилятор загружает в него нужное значение [ L %r0,172(,%r15) ].

Единственный способ избежать ошибки при использовании модификаторов – это обратиться к описанию соответствующей ассемблерной инструкции перед ее использованием. За более подробной информацией обратитесь к публикации IBM SA22-7832-10 z/Architecture Principles of Operation (EN). Ссылка на официальную версию также приведена в разделе Ресурсы.

Список выходных операндов

В списке выходных операндов перечисляются все выходные операнды. Этот список может быть пустым, если не содержит ни одного операнда. В этом случае список выходных операндов сокращается до единственного оператора встроенного ассемблера ":". Операнды разделяются в списке запятыми. Каждый операнд состоит из обязательного модификатора ("+" или "="), ограничителя и заключенного в круглые скобки выражения C/C++. Значение выражения C/C++ используется в качестве выходного операнда ассемблерных инструкций в операторе встроенного ассемблера. Выходные операнды должны являться модифицируемыми l-значениями.

Список входных операндов

В списке входных операндов перечисляются все входные операнды. Этот список может быть пустым, если не содержит ни одного операнда. В этом случае список выходных операндов сокращается до единственного оператора встроенного ассемблера ":". Операнды разделяются в списке запятыми. Каждый операнд состоит из ограничителя и заключенного в круглые скобки выражения C/C++. Значение выражения C/C++ используется в качестве входного операнда ассемблерных инструкций в операторе встроенного ассемблера.

Список экранирования

Список экранирования (clobber list) может включать в себя (a) области памяти, (b) коды условий и (с) имена регистров. Все элементы списка должны быть заключены в двойные кавычки и разделены запятыми. Список экранирования информирует компилятор о модификациях, которые могут выполняться ассемблерными инструкциями над элементами, не перечисленными в списках входных или выходных операндов.

(a) Добавление области памяти в список экранирования

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

(b) Добавление кода условия в список экранирования

Многие ассемблерные инструкции (например, Compare, Add и Subtract) изменяют коды условий. Программист должен сообщить об этом компилятору, добавив в список экранирования элемент " cc". Полный перечень ассемблерных инструкций, изменяющих коды условий, приведен в документе z/Architecture Principles of Operation (EN).

(c) Добавление имен регистров в список экранирования

Если ассемблерная инструкция использует или обновляет значения регистров, не перечисленных в списках входящих и исходящих операндов, эти регистры должны быть перечислены в списке экранирования. Используя информацию из этого списка, компилятор обеспечивает выполнение операций оператора встроенного ассемблера. Если оператор ассемблера использует регистр, не перечисленный в списке экранирования, то компилятор не узнает об использовании этого регистра и может задействовать его для других целей. В результате может возникнуть ошибка в вычислениях.

Обратите внимание, что некоторые регистры зарезервированы компилятором для собственных нужд, и добавлять их в список экранирования нельзя. В таблице 2 перечислены некоторые регистры, зарезервированные компиляторами IBM XL.

Таблица 2. Назначение регистров, используемых компиляторами IBM XL платформы z Systems
Имя регистраСпециальное назначениеВозможность использования пользователем
r2, r3 Параметры, возвращаемые значения Да, с осторожностью
r4, r5, r6 Параметры Да, с осторожностью
r13Базовый регистр пула литераловНет
r14 Адрес возврата Да, с осторожностью
r15Указатели стекаНет

Пример использования списка экранирования подробно рассмотрен в статье Advanced features of inline assembly for Linux on z Systems (EN).


Заключение

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

Из этой статьи вы узнали об основах встроенного ассемблера для Linux на платформе z Systems. Расширенные возможности будут рассмотрены в статье Advanced features of inline assembly for Linux on z Systems (EN).


Благодарности

Автор выражает благодарность Висду Вокшури (Visda Vokhshoori) и Нья-Ви Трану (Nha-Vy Tran) за помощь в работе над этой статьей.


Ресурсы


Ссылки

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=1029165
ArticleTitle=Основы встроенного ассемблера для Linux на платформе z Systems
publish-date=03302016