Ада-95. Компилятор GNAT

       

Ограничения


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

Это значит, что мы указываем компилятору то, как мы интерпретируем смысл этой переменной.

Например:

Unsigned_32'Asm_Output ("=m", Eax);

Здесь, использование ограничения m (memory) указывает компилятору, что переменная Eax должна быть переменной которая размещается в памяти.

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

Рассмотрим еще один пример указания ограничения:

Unsigned_32'Asm_Output ("=r", Eax);

Здесь, использование ограничения r (register) указывает компилятору на использование регистровой переменной.

Следовательно, для хранения этой переменной компилятор будет использовать регистр процессора.

Если ограничению предшествует символ равенства ("="), то это указывает компилятору, что переменная будет использоваться для сохранения данных.

В показанном ранее примере, при указании ограничения, использовалось ограничение g (global), что позволяет оптимизатору использовать то, что он сочтет более эффективным.

Следует заметить, что существующее число различных ограничений достаточно обширно.

При этом, для процессоров архитектуры Intel x86 наиболее часто используемыми ограничениями являются:

= ограничение вывода
g  -  глобальная переменная (т.е. может быть чем угодно)
m  -  переменная в памяти
I  -  константа
a  -  использовать регистр eax
b  -  использовать регистр ebx
c  -  использовать регистр ecx
d  -  использовать регистр edx
S  -  использовать регистр esi
D  -  использовать регистр edi
r  -  использовать один из регистров eax, ebx, ecx или edx
q  -  использовать один из регистров eax, ebx, ecx, edx, esi или edi
<


При использовании другого типа процессора или при необходимости получения более исчерпывающей информации об ограничениях, следует обратиться к руководствам по gcc и as.

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

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

Теперь необходимо рассмотреть то, как указать компилятору где эта переменная находится.

Для связывания параметра вывода с ассемблерным операндом используется %x-нотация, в которой x является числом.

Asm ("pushfl" & LF & HT & -- сохранить регистр флагов в стеке "popl %%eax" & LF & HT & -- загрузить флаги из стека в регистр eax "movl %%eax, %0", -- сохранить значение флагов в переменной Outputs => Unsigned_32'Asm_Output ("=g", Eax));

Таким образом, в показанном выше фрагменте кода, %0 будет заменяться фактическим кодом, в соответствии с решением компилятора о фактическом месторасположении переменной Eax.

Означает-ли это, что мы можем иметь только одну переменную вывода?

Нет, мы можем иметь их столько сколько нам необходимо.

Это работает достаточно просто:

можно описать множество параметров вывода разделяя их запятыми и завершая список символом точки с запятой


  • отсчет операндов ведется в последовательности %0, %1, %2

    и т.д., начиная с первого параметра вывода

    Для демонстрации сказанного, приведем простой пример:

    Asm ("movl %%eax, %0" & "movl %%ebx, %1" & "movl %%ecx, %2", Outputs => (Unsigned_32'Asm_Output ("=g", Eax"), -- %0 = Eax (Unsigned_32'Asm_Output ("=g", Ebx"), -- %1 = Ebx (Unsigned_32'Asm_Output ("=g", Ecx)); -- %2 = Ecx

    Следует нпомнить с чего начиналось написание нашего примера:



    Asm ("pushfl" & LF & HT & -- сохранить регистр флагов в стеке "popl %%eax" & LF & HT & -- загрузить флаги из стека в регистр eax "movl %%eax, %0", -- сохранить значение флагов в переменной Outputs => Unsigned_32'Asm_Output ("=g", Eax));

    Фактически, мы можем использовать регистровое ограничение для указания компилятору на необходимость сохранять содержимое регистра eax в переменной Eax

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

    with Interfaces; use Interfaces; with Ada.Text_IO; use Ada.Text_IO; with System.Machine_Code; use System.Machine_Code; with Ada.Characters.Latin_1; use Ada.Characters.Latin_1;

    procedure Flags is

    Eax : Unsigned_32; begin

    Asm ("pushfl" & LF & HT & -- сохранить регистр флагов в стеке "popl %%eax", -- загрузить флаги из стека в регистр eax Outputs => Unsigned_32'Asm_Output ("=a", Eax)); Put_Line ("Flags register:" & Eax'Img); end Flags;

    Примечательно, что ограничение a, указывает компилятору, что переменная Eax будет располагаться в регистре eax.

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

    #APP pushfl popl %eax #NO_APP movl %eax,-40(%ebp)

    Очевидно, что значение eax сохранено в Eax компилятором после выполнения кода ассемблера.

    Следя за последовательностью обсуждения, внимательный читатель может быть удивлен необходимостью сохранения содержимого регистра eax в переменной Eax, когда значение регистра флагов можно сразу вытолкнуть (pop) в переменную вывода.

    Следует согласиться с тем, что такое удивление справедливо.

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

    Таким образом, мы можем переписать исходный текст следующим образом:

    with Interfaces; use Interfaces; with Ada.Text_IO; use Ada.Text_IO; with System.Machine_Code; use System.Machine_Code; with Ada.Characters.Latin_1; use Ada.Characters.Latin_1;

    procedure Okflags is

    Eax : Unsigned_32; begin

    Asm ("pushfl" & LF & HT & -- сохранить регистр флагов в стеке "pop %0", -- загрузить флаги из стека в переменную Eax Outputs => Unsigned_32'Asm_Output ("=g", Eax)); Put_Line ("Flags register:" & Eax'Img); end Okflags;

    В результате, мы получим результат, который был показан ранее.


    Содержание раздела