Конструкция вида asm() полезна при необходимости реализации низкоуровневых операций и достижения максимальной эффективности кода. Она присутствует практически во всех языках программирования, например:
asm (
"R2 = 0;"
);
или
asm ("R1 = 2; \
R3 = 4;"
);
Компилятор не анализирует код внутри конструкции asm() и передает его напрямую ассемблеру. Единственное, что делает компилятор – выполняет макроподстановки параметров вместо %0,...,%9, если внутри asm()-конструкции используется шаблон.
Одной из трудностей использования asm-вставок являются тонкости использования в ассемблерном коде различных классов регистров: как будет показано ниже, некоторые регистры компилятор использует монопольно и их нельзя модифицировать, некоторые доступны для использования с последующим восстановлением, а на использование остальных вообще нет ограничений. К тому же следует учитывать возможные оптимизации при генерации программного кода.
Поскольку компилятор не анализирует код внутри asm-вставки, то вполне возможно "испортить" те регистры, которые задействованы компилятором для других целей, и он предполагает, что работа с ними – его прерогатива. Поэтому cc21k поддерживает использование шаблонов внутри asm-вставок. При этом для программиста отпадает необходимость прямого (явного) использования регистров в ассемблерных инструкциях: ему достаточно указать процессорные регистры какого типа следует использовать и компилятор сам выберет "свободные" регистры и выполнит их подстановку.
|
|
Синтаксис шаблона для asm-конструкции выглядит следующим образом:
asm (
template
[: [constraint (output_operand) [,constraint (output_operand)...]]
[: [constraint (input_operand) [,constraint (input_operand)...]]
[: clobber ]]]
);
где
template – строка в кавычках, содержащая ассемблерные инструкции с символами %число вместо наименований регистров в тех позициях, куда компилятор должен сам подставить регистры. Операнды нумеруются в порядке появления слева направо, от 0 до 9;
constraint – строка специального вида (в кавычках), указывающая компилятору на то, процессорные регистры какого типа следует использовать для каждого из входных/выходных операндов;
output_operand – имя переменной программы С/С++, в которую следует записать результат ассемблерной инструкции;
input_operand – имя переменной программы С/С++, из которой берется значение для выполнения ассемблерной инструкции;
clobber – список регистров (каждый в кавычках, маленькими буквами), которые явно использованы (модифицированы) программистом в данной ассемблерной вставке, чтобы компилятор, при необходимости сгенерировал дополнительный код по их сохранению и восстановлению.
|
|
Типы регистров:
Constraint | Register type | Registers |
a | DAG2 B registers | b8 —b15 |
b | Q2 R registers | r4 —r7 |
c | Q3 R registers | r8 —r11 |
d | All R registers | r0 —r15 |
e | DAG2 L registers | l8 —l15 |
F | Floating-point registers | F0 —F15 |
f | Accumulator register | mrf, mrb |
h | DAG1 B registers | b0 —b7 |
j | DAG1 L registers | l0 —l7 |
k | Q1 R registers | r0 -r3 |
l | Q4 R registers | r12 -r15 |
r | All general registers | r0 —r15, i0 —i15, l0 —l15, m0 —m15, b0 —b15, ustat1, ustat2 |
u | User registers | ustat1, ustat2 (also ustat3, ustat4 on ADSP-2116x DSPs) |
w | DAG1 I registers | I0 —I7 |
x | DAG1 M registers | M0 —M7 |
y | DAG2 I registers | I8 —I15 |
z | DAG2 M registers | M8 —M15 |
=& constraint | Indicates that the constraint is applied to an output operand that may not overlap an input operand | |
=constraint | Indicates that the constraint is applied to an output operand |
Использование других букв, как указано в руководстве по компилятору, приводит к непредсказуемому поведению компилятора при выборе регистров
Примеры:
Исходный код на С | Сгенерированный код на ассемблере |
{ int result, x, y; asm ( "%0 = %1 + %2;" : "=d" (result) : "d" (x), "d" (y) : ); } | r2=dm(_x); r1=dm(_y); r0 = r2 + r1; dm(_result)=r0; |
asm ( "r9 = %1; \ r10 = %2; \ %0 = r9+r10;" : "=d" (result) /* %0 */ : "d" (x), "d" (y) /* %1, %2 */ : "r9", "r10" ); | modify(i7,-2); dm(-3,i6)=r9; dm(-2,i6)=r10; r2=dm(_x); r1=dm(_y); r9 = r2; r10 = r1; r0 = r9+r10; dm(_result)=r0; r9=dm(-3,i6); r10=dm(-2,i6); |
"0" в операндах!!!!!! (Ix) |
Ограничения при использовании ассемблерных вставок:
- нельзя никаким образом передавать управление внутри asm-вставки (изменять ход выполнения программы);
- переменные C/C++ программы доступны только путем указания их в списке операндов/результатов;
- все изменяемые в явном виде регистры должны быть обязательно внесены в список clobber.
Пропроцессор запускается перед компилятором и не просматривает вставки asm(). Поэтому внутри вставок проблематично использовать названия регистров IOP-процессора, определенных в файле def21x60.h как константы адресов памяти.