Всем добрый день.
Есть функция на "си" cvavr. Скажем int SUM(int a, int b).
Сложение необходимо сделать на ассемблере, как можно этой вставке передать два двухбайтных параметра, и как потом вернуть результат из вставки обратно в функцию?
Как вариант конечно можно попробовать.
Но хотелось бы более "правильное" решение.
Как понимаю, такой тип переменных есть фичером cvavr, и другие компиляторы будут ругаться.
И не есть такое решение "религиозно правильное".
Выше уже дали ответ - ищем Calling Assembly Functions from C, или лучше C Calling conventions для интересующего компилятора. В найденном документе будет описаны соглашения о вызове функций: как параметры передаются и возвращаются.
Вариант номер два, для ленивых. Создаем простейшую функцию с желаемой сигнатурой, компилируем, изучаем полученный ассемблерный код, ..., профит!
там написано, что параметры передаются через стек, к ним можно обратится через "y+", эта часть понятна.
Но непонятен один момент.
Если я в своей ассемблерной функции, захочу записать какое-то значение в рекистр R30, скажем. К чему это может привести?
Ведь у меня, в этом регистре, может хранится какая-то переменная, скажем глобальная? Не получится ли так, что записав туда свое значение, я потеряю то, что там было раньше?
А так понял, что если я буду где-то в ассемблерной вставке использовать какие-то регистры, то должен держать эти регистры пустыми, и не позволять компилятору ничего глобального в них размещать.
Я так понял, что заблокировать какие-то регистры нельзя напрямую.
Но можно к регистру приписать какую-то переменную (как посоветовали в первом ответе), и выключить Smart register allocation.
В таком случае проподает необходимость из ассемблера обращаться к параметрам функции через стек, а просто пере вызовм такой функции, записывать необходимое значение в регистровую переменную, и из ассемблера она уже будет доступна "там где нужно".
Не совсем красиво конечно, но тоже может быть. Похоже, что это издержки компилятора.
Правильно думаю будет, перед вызовом ассемблерной вставки (либо внутри вставки), сохранить текущее значение используемого регистра, записать в него то, что нужно, сделать действия, а при выходе из вставки, восстанавливать старое значение.
А так понял, что если я буду где-то в ассемблерной вставке использовать какие-то регистры, то должен держать эти регистры пустыми, и не позволять компилятору ничего глобального в них размещать.
Ерунда какая то
Вы можете в своей asm вставке сделать следующее
A very special extra role is defined for the register pairs R26:R27, R28:R29 and R30:R31. The role is so important that these pairs have extra names in assembler: X, Y and Z. These pairs are 16-bit pointer registers, able to point to adresses with max. 16-bit into SRAM locations (X, Y or Z) or into locations in program memory (Z).
void fill_temp_buffer(unsigned int data, unsigned int adr){
while (SPMCSR&1); //wait for spm complete
#asm
push r30
push r31
push r20
push r0
push r1
ldi r20, 1 ;//put CMD to r20
ld r30, y ;//move CurrentAddress LSB to Z pointer
ldd r31, y+1 ;//move CurrentAddress MSB to Z pointer
ldd r0, y+2 ;//move data LSB
ldd r1, y+3 ;//move data MSB
sts 0x68, r20 ;//move CMD to SPM control register
spm ;//store program memory
pop r1
pop r0
pop r20
pop r31
pop r30
#endasm
}
Но, остался непонятен один вопрос, теоретический.
Почему вызов команды push не вызывает увеличение указателя стека?
С помощью push, я сохраняю в стеке значение пяти регистров (я так думаю). Соответственно, что-бы после этого обратится к моим параметрам функции, я должен делать так:
ldd r30, y+5 ;//move CurrentAddress LSB to Z pointer
ldd r31, y+6 ;//move CurrentAddress MSB to Z pointer
ldd r0, y+7 ;//move data LSB
ldd r1, y+8 ;//move data MSB
Но если я так делаю, то у меня ничего не работает.
С чем это может быть связано?
Подозреваю, что команда push сохраняет значение в другой стек "hardware stack"? а передача параметров в функцию идет через "data stack", на его вершину казывает "y". Так ли это?
Почему вызов команды push не вызывает увеличение указателя стека?
А куда он должен увеличится, Вы так сказать физически представляете где он находится?
Это ведь самый конец области SRAM-в стандартном (default) его определении.Правда его можно переназначать куда угодно , но Вам пока это не надо. И называется этот адрес ВЕРШИНА стека. Выше этой точки , для человека, только облака, озоновый слой, Господь Бог.
Для процессора- неконтролируемое падение вниз и "убивание" всех данных начиная с регистров и заканчивая SRAM.
Поэтому команда PUSH , уменьшает указатель адреса т.е. спускает Вас с горы плавненько так и без эксцессов. А команда POP увеличивает - поднимает Вас в гору.
Да, в даташите так и есть.
The Stack Pointer Register always points
to the top of the Stack. Note that the Stack is implemented as growing from higher memory locations
to lower memory locations. This implies that a Stack PUSH command decreases the Stack
Pointer.
Еще там есть такое:
The Stack is mainly used for storing temporary data, for storing local variables and for storing
return addresses after interrupts and subroutine calls.
А через какой stack передаются параметры в функуию, при ее вызове?
почему после вызова 5раз push.
вот этот код не работает?
ldd r30, y+5 ;//move CurrentAddress LSB to Z pointer
ldd r31, y+6 ;//move CurrentAddress MSB to Z pointer
ldd r0, y+7 ;//move data LSB
ldd r1, y+8 ;//move data MSB
ld r30, y ;//move CurrentAddress LSB to Z pointer
ldd r31, y+1 ;//move CurrentAddress MSB to Z pointer
ldd r0, y+2 ;//move data LSB
ldd r1, y+3 ;//move data MSB
т.е. почему после вызовов push не нужно увеличивать и y?
почему после вызова 5раз push.
Если уж быть точным то как минимум 7. Всё зависит от памяти программ процессора.
Первым , и от вас не зависящим в стек уходит адрес возврата из функции, затем стек отдается на "разгробление" программисту. Сколько он посчитает нужным сохранить данных , столько раз он напишет push.
ldd r30, y+5 ;//move CurrentAddress LSB to Z pointer
ldd r31, y+6 ;//move CurrentAddress MSB to Z pointer
ldd r0, y+7 ;//move data LSB
ldd r1, y+8 ;//move data MSB
То это косвенная адресация к SRAM , комманда LDD давно заменена на LD. Расшифрую , что здесь написано
Загрузить регистр R30, данными которые надо взять из ячейки SRAM находящейся по адресу Y (какой-то базовый адрес) +5 . Если Y=60 , то данные в R30 загрузятся из адреса 65
Следующая строка R31 получит данные с адреса 66 ну и т.д 67/ Но , что Вы хотите сделать - мне не ясно
Получается так, что адрес хранящийся в y и адрес вершины стека это разные "вещи"?
И вызов команды push не вызывает увеличение y?
Куда в таком случае указывает у, на какую часть памяти проца, где она физически находится (по умолчанию)?
Использование оперативной памяти расписано на странице "RAM Memory Organization and Register Allocation", вот картинка оттуда.
Стек данных используется для динамического хранения локальных переменных, передачи параметров функций и для сохранения значений регистров при прерывании.
Согласен, именно эта картинка из хелпа меня и смущает...
Есть два стека, один аппаратный, другой програмный.
"Стек данных используется для динамического хранения локальных переменных, передачи параметров функций и для сохранения значений регистров при прерывании."
Обращение к этому стеку идет через Y, так понимаю для ассемблера y и Y и R32/33 это одно и то же?
И команда push, кладет значение регистра в аппаратный стек, а не в програмный? Правильно?
Для этого у меня не происходит "съезд" указателья Y, после вызова push?
push param3
push param2
push param1
call FucnAddr
FuncAddr:
pop param1
pop param2
pop param3 //Куда попать полученные данные без разницы. хоть в выделеную память хоть по регистрам, на выбор.
//производим действия с параметрами
//размещаем в eax результат выполнения (
mov eax, result
ret
обязательно вычищаем всё что push, pop. Иначе ret не сможет отработать нормально, потому-что адрес возврата тоже кладётся в стек. Сейчас лень вспоминать многое. Поэтому можете рассказать подробнее что нужно. Приходилось изучать вопрос этого симбиоза.
Естественно, если мы используем си, то там за нас уже всё сделали. И по идее можно подставлять параметры уже готовые. Например:
int Test(char a,char b) {
__asm {
mov eax, a
mov ebx, b
sub eax, ebx
ret // по логине это затрёт наше нижнее значение и вернёт результат.
};
return 0;
}
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
int Test(int a,int b);
int main(int argc,char* argv[])
{
int res=Test(10,5);
printf("Hello World! %d",res);
cin.get();
return 0;
}
int Test(int a,int b) {
__asm {
mov eax, a
mov ebx, b
sub eax, ebx
//начиная отсюда, реализуется код который вставляет компилятор для обработки.
pop edi
pop esi
pop ebx
add esp,0C0h
cmp ebp,esp
mov esp,ebp
pop ebp
ret // по логине это затрёт наше нижнее значение и вернёт результат.
};
return 0;
}
Чем дальше, тем больше вопросов...С ассемблером знаком пару дней, много не ясно.
1 (основной).
В какой стек команда push кладет свой аргумент. В аппаратный стек или програмный (стек данных)?
2. почему сделано разделение на два стека?
Почему бы все не хранить в одном стеке (локальные переменные, параметры функций, значения записанные туда с помощью push, адреса возвратов. Ведь все-равно, после выхода из функции, все локальные переменные удаляются, соответственно компилятор мог-бы автоматически удалять все эти временные данные.