Skocz do zawartości

[C] Przyśpieszanie, optymalizacja kodu. Wstawki asemblerowe w c.


Pomocna odpowiedź

deshipu, napisałem Ci kilka postów wyżej co możesz zrobić. Mam Ci za to zapłacić?

Myślę, że raczej cię na to nie stać.

[ Dodano: 27-01-2017, 16:40 ]

Ale jest dla ciebie lepszy sposób. Możesz po prostu zacząć się zachowywać jak normalny człowiek, a nie leniwy cwaniaczek i wtedy po prostu nie będę miał co tobie wytykać. Możesz zacząć najpierw samemu się rozglądać za odpowiedzią, zarówno na tym forum jak i w szerszym zakresie, zanim zadasz jakieś pytanie (a jak już ją znajdziesz, to możesz o niej napisać). Możesz odpowiadać na pytania, które ci tu ludzie zadają. Ba, możesz nawet sam zacząć pomagać i w ten sposób odwdzięczyć się jakoś tej społeczności za czas, który ci poświęciła. Bardzo dużo możesz, wystarczy tylko chcieć. Ja się nie przyczepiłem do ciebie personalnie ze złośliwości -- tak samo traktuję każdą inną osobę, która się zachowuje tak jak ty. Wszystko jest w twoich rękach!

deshipu, napisałem Ci kilka postów wyżej co możesz zrobić. Mam Ci za to zapłacić?

Myślę, że raczej cię na to nie stać.

[ Dodano: 27-01-2017, 16:40 ]

Ale jest dla ciebie lepszy sposób. Możesz po prostu zacząć się zachowywać jak normalny człowiek, a nie leniwy cwaniaczek i wtedy po prostu nie będę miał co tobie wytykać. Możesz zacząć najpierw samemu się rozglądać za odpowiedzią, zarówno na tym forum jak i w szerszym zakresie, zanim zadasz jakieś pytanie (a jak już ją znajdziesz, to możesz o niej napisać). Możesz odpowiadać na pytania, które ci tu ludzie zadają. Ba, możesz nawet sam zacząć pomagać i w ten sposób odwdzięczyć się jakoś tej społeczności za czas, który ci poświęciła. Bardzo dużo możesz, wystarczy tylko chcieć. Ja się nie przyczepiłem do ciebie personalnie ze złośliwości -- tak samo traktuję każdą inną osobę, która się zachowuje tak jak ty. Wszystko jest w twoich rękach!

A mnie się zdawało, że forum jest po to żeby zadawać pytania kiedy nie można znaleźć odpowiedzi we własnym zakresie bez względu na powód tej niemożności. Zdążyłem się już zorientować, że wiele zarejestrowanych na tym forum osób bez względu na wiek nie zadaje żadnych pytań z obawy przed tego typu odpowiedziami jakie tu co poniektóre autorytety serwują i tego typu "normalnością" jaką np. Ty prezentujesz czyli m.in chwaląc się wiedzą jednocześnie nie chcąc się nią dzielić jakby licząc na to, że ktoś kto jej potrzebuje zacznie się do Ciebie łasić... (obrzydliwe).

Myślę, że raczej cię na to nie stać.

A ja myślę, że dużo by mnie to nie kosztowało, nie więcej jak dwie sztuki i to obie z lewej bo tam Ci wystaje najbardziej...

Skończ tę żenadę gościu i nie rób tu więcej offtopu.

------------------------------------------------------------------------------------------------

Przeglądałem przykłady do których odesłał mnie marek. Niestety nie były bardzo użyteczne. Co prawda można się lepiej zorientować ogólnie co do zasad rządzących assemblerem dla avr ale widocznie za głupi jestem żeby móc je praktycznie zastosować we własnym programie. Niestety nie znalazłem do tej pory użytecznych przykładów więc stoję w tym temacie 🙁 Jeśli jednak znalazł by się ktoś mniej "normalny" od deshipu i zobrazował temat w kilku linijkach kodu to będę zobowiązany.

---------------------------------

EDIT: Dwie sztuki mostków prostowniczych rzecz jasna, żeby nikt się nie musiał zastanawiać o co mi chodziło 😉

Przepisane żywcem z książki A. Witkowski "Mikrokontrolery AVR" :

void s_del(uint8_t cnt){
uint8_t tmp;
asm volatile(
"ldi %0,%[ticks]"  "\n"
"1: dec %0"        "\n\t"
"brne 1"           "\n\t"
:"=d" (tmp)
: [ticks] "M" (cnt));}

void main(void){
uint8_t counter = 10;

while(1){
 s_del(counter);
}

}

Może mi ktoś powiedzieć dlaczego ta wstawka się nie kompiluje?

Kompilator wyrzuca błąd:

Compiling C: main.c
avr-gcc -c -mmcu=atmega8 -I. -gstabs -DF_CPU=16000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./main.lst  -std=gnu99 -MMD -MP -MF .dep/main.o.d main.c -o main.o 
main.c: In function 's_del':
main.c:24:1: warning: asm operand 1 probably doesn't match constraints
asm volatile(
^
main.c:24:1: error: impossible constraint in 'asm'
make.exe: *** [main.o] Error 1

Ech, szkoda mi Ciebie chłopie. Podskakujesz, próbujesz po kątach mądrych ludzi rozstawiać, oceniać ich pomoc a tu proszę: pierwszy z brzegu kłopot i leżysz. Napiszę z litości, żebyś nie miał wątpliwości. Bo tu na Forum nikt Cię nie zaprasza i nikt nie będzie po Tobie płakał jeśli Ci się nie podobają reguły gry. A są takie, że ja pomagam na moich własnych zasadach albo wcale - dotarło? Więc oszczędź sobie jadu, gróźb i aluzji, nawet między wierszami, a ja oszczędzę Ci prób domyślania się: tak, zły świat jest przeciwko Tobie, a ja jako zły człowiek - również. Tak więc znów nie dostaniesz działającego przykładu a tylko wskazówkę. Idziesz w to?

Swoją drogą Twój przypadek jest moim zdaniem potwierdzeniem hipotezy, że jedzenie przykładów jest marnym sposobem nauki, prawda? Zobacz ilu ludzi zabiło się próbując zbudować latający samolot/szybowiec mając tyle przykładów nad głową. Wychodzili z siebie by zbudować wierną kopię, modyfikowali coś tu i tam na czuja a potem "lądowali" ze złamanym kręgosłupem lub prosto do piachu. Dopiero próba dogłębnego zrozumienia mechaniki lotu, zasad stateczności i praw fizyki stojących za unoszeniem się ptaków w powietrzu pozwoliła zbudować coś sensownego. Potrzebna była gruntowna wiedza a nie przykłady, których nie rozumieliśmy prawda? Dalej to już tylko nasza technika i jej niezawodność. A bez wiedzy jesteś jak skaczący z wieży klasztoru gość oblepiony miodem i piórami - to nie wróży dobrze, prawda?

A wracając do s_del: nie mam tej książki, nie chcę jej kupować ani ściągać nielegalnie więc nie umiem potwierdzić Twoich słów o przepisaniu żywcem. Albo więc zrobiłeś jakieś poprawki lub złożyłeś to bezrozumnie z kliku przykładów, albo Pan Autor nie bardzo rozumie co pisze (w co wątpię). W obu przypadkach ktoś kto ten przykład naskrobał ma braki: w liście instrukcji AVR, w rozumieniu zasad przekazywania parametrów do funkcji GCC, w znajomości reguł pisania wstawek asemblerowych - czyli mamy trzy podstawy o których pisałem wcześniej.

Zatrzymajmy się więc na tym przykładzie, może już bez dalszego kopania leżących. Jeśli masz ochotę zagrać i dojść samodzielnie do działającej funkcji s_del, zróbmy to krok po kroku. W pierwszym podejściu napisz nam tutaj proszę:

1. Jak rozumiesz jej działanie? Co ma robić tak ogólnie? Co dostaje na wejściu i po co?

2. Opisz linia po linii te trzy instrukcje w niej zawarte - co to za instrukcje procesora AVR, co robią, jakich argumentów potrzebują i po co w ogóle one tu są.

3. Zajrzyj do opisu składni języka asemblera: w jaki sposób buduje się symbole/etykiety, z jakich znaków mogą się składać itp.

Jeśli napiszesz coś z sensem (a przy okazji znajdziesz pierwsze błędy) będę wiedział, że masz wystarczającą wiedzę by pójść dalej. Ja wiem jakie są odpowiedzi, ale jak zwykle chcę zmusić Cię do poszukiwań u źródeł. Pytania szczegółowe dot. tematu także mile widziane. Jęczenie ("Jestem za głupi") lub sentencje ogólne ("Co ty sobie wyobrażasz?" itd) raczej utwierdzą mnie w przekonaniu, że znów marnuję swój czas. Błędów jest kilka a chętnych do pomocy jakoś mało.. No, to do roboty 🙂

Nie będę aż tak okrutny i zły jak reszta tego świata i pozwolę Ci się wykazać .. eee.. wujku.

Nie bierz mi tego za złe, w końcu to nawet trochę w stylu jaki często serwujecie na forum Ty i ten Twój deshipu. Próbowałem grzecznie i kulturalnie ale do was jak w betonowy mur więc jest jak jest. Pisałeś coś wcześniej, że nie piszesz tu po to żeby zapełnić forum bo m.in (te inne z grzeczności pominę) nie masz na to czasu a tym czasem wylewasz z siebie referaty w sposób sobie swoisty przepełnione "jadem", jak sam to nazwałeś, które często z merytoryką nie mają wiele wspólnego. Wyjaśnienia wymaga jeszcze jedna kwestia. Wspomniałeś coś o "zrozumieniu zasad gry" na forum, więc aby nie było niejasności pytam wprost: co tu jest grane??

To chyba tyle słowem wstępu przejdźmy więc do meritum 🙂

1. Jak rozumiesz jej działanie? Co ma robić tak ogólnie? Co dostaje na wejściu i po co?

Otóż nazwę autorsko skróciłem. W oryginale było "short_delay" przedstawiona w formie makra. Jest to funkcja opóźniająca a długość opóźnienia zależy od wartości przyjmowanego argumentu "cnt" używanego dalej we wstawce pod nazwą operandu [ticks].

2. Opisz linia po linii te trzy instrukcje w niej zawarte - co to za instrukcje procesora AVR, co robią, jakich argumentów potrzebują i po co w ogóle one tu są.

1) ldi %0,%[ticks] ; instrukcja ldi kopiuje zawartość [ticks] do rejestru zmiennej pomocniczej "tmp" zdefiniowanej przed wstawką a użytej we wstawce jako operand bez nazwy któremu został przydzielony nr. 0.

2) 1: dec %0 ; instrukcja "dec" dekrementuje zawartość rejestru operandu 0. Tutaj użyta po etykietce "1:"

3) brne 1 ; tutaj mój błąd w przepisywaniu. powinno być "1b" co jak mniemam nie robi zasadniczej różnicy. Instrukcja "brne" (Branch if Not Equal) czyli "odgałęź jeśli nie jest równe (0)" po której podane jest miejsce docelowe tego "odgałęzienia" czy też skoku. W tym wypadku skocz do etykietki "1" gdybym przepisywał uważnie i byłoby "1b" znaczyło by to 1 instrukcja w tył. (Próbowałem na oba sposoby i efek taki sam)

:"=d" (tmp) ; zmienna "tmp" jest tu użyta jako operand wyjściowy z ograniczeniem "=d". Modyfikator "=" określa operand jako tylko do zapisu. Typ operandu "d" odnosi się do rejestrów od r16 do r31. (próbowałem na wszelkie sposoby, chyba wszystkie możliwe - bez skutku)

: [ticks] "M" (cnt) ; zmienna "cnt" jako operand wejściowy, któremu została przypisana nazwa [ticks] a jego typem jest "M" czyli 8-bitowa stała (lub zmienna) z pamięci.

Problem zasadniczo polega na tym, że przy próbie użycia operandów wyjściowych wstawki się nie kompilują. Kiedy natomiast operandy wyjściowe nie są używane z kompilacją nie ma problemu. Powyższą wstawkę równie dobrze można by napisać i w taki sposób:

asm volatile(".rep %[ticks]"  "\n\t"
			"nop"		  "\n\t"
			".endr"		  "\n\t"
			::[ticks] "M" (cnt));

Tylko, że ja potrzebuję tych operandów wyjściowych... Celowo wybrałem te proste przykłady żeby było można łatwo zobrazować istotę problemu. Tak więc jak napisałeś: nie radzę sobie z trzema linijkami kodu..

3. Zajrzyj do opisu składni języka asemblera: w jaki sposób buduje się symbole/etykiety, z jakich znaków mogą się składać itp.

Już. Nie pomogło 🙁

Kończ Waść, wstydu oszczędź.

O, trochę o temacie zapomniałem a tu proszę, jakie sprawozdanko się pojawiło. Ok, to tak:

1. Z tematu "Co ta funkcja ma robić?" zdałeś. Chodziło mi o to co robi i jakich argumentów oczekuje. Najważniejsze to to, że spodziewa się jednego argumentu typu uint8_t czyli bajtu bez znaku. W swojej konwencji przekazywania parametrów do funkcji kompilator GCC umieści go zatem w R24. Przeczytaj to poniżej a będziesz wiedział czego się spodziewać w przyszłości:

https://gcc.gnu.org/wiki/avr-gcc#Calling_Convention

2. Z tematu opisu instrukcji trochę przedobrzyłeś. Chodziło mi o czysto asemblerowe rozumienie tych instrukcji, bo tu jest pierwszy błąd. Zobacz, napisałeś:

" instrukcja ldi kopiuje zawartość [ticks] do rejestru zmiennej pomocniczej "tmp"

i to jest bełkot niczego nie wnoszący, bo tak można powiedzieć o każdej instrukcji przesyłania danych: kopiuje coś tam gdzieś tam. A problem leży w tym, że LDI jest ładowaniem (LoaD) argumentu natychmiastowego (Immediate) do rejestru. Koniec, jedno krótkie zdanie. A gdybyś był już za pan brat z trybami adresowania (miałeś kilka dni) to wiedziałbyś, że argument natychmiastowy leży w samej instrukcji, jest liczbą zaszytą w jej kodzie operacyjnym. Z tego prosty wniosek, że ona nie może być tu użyta, bo w czasie kompilacji nie wiadomo przecież z jakim argumentem zostanie wywołana ta funkcja. Jeśli chcesz dostać się do [ticks] które leży w rejestrze (już wiemy że w R24, ale to nie ma znaczenia) musisz użyć zwykłego MOV. To rzeczywiście kopiuje zawartość jednego rejestru do drugiego. Tak więc po użyciu:

MOV %0,%[ticks]

kompilator podstawi jakiś rejestr pod %0 i ten w którym funkcja otrzymała argument za %[ticks].

Dalej mamy odliczanie i to jest proste a na końcu skok warunkowy zamykający pętlę. Trochę mi smutno, że słowo pisane do Ciebie nie dociera (ach ten jad) bo skoro czytasz i nic, to rokowania są słabe. A w składni języka asemblera masz opisane jak tworzyć etykiety (miałeś właśnie na to zwrócić uwagę - coś zostało w głowie?) czyli rodzaj symboli. To muszą być ciągi znaków z określonego zestawu zaczynające się literą lub znakiem _. A Twoja etykieta składa się ze znaku 1 (jedynki). Rozumiem przepisywanie z książki, ale znając zasady nie wpisałbyś tu nigdy cyfry, przynajmniej nie na początku. Podejrzewam, że w oryginale była mała litera "L" czyli "l" (od słowa "label") wyglądająca w druku jak jedynka. Ciągi zaczynające się cyfrą są traktowane jak liczby a tu liczby wstawić nie możesz, to robi dopiero linker na etapie konsolidacji i lokowania kodu wynikowego. Tak więc: etykiety mogą być długie i coś mówiące (jak zmienne w C), ale muszą zaczynać się od litery a kończyć dwukropkiem:

petla_37:

wczytujemy_wejscie3:

koniec_funkcji:

itd... To konwencja wielu asemblerów.

Coś tam jeszcze było.. acha, argumenty wejściowe. Otóż cnt alias ticks dostało tam typ "M" a teraz już sam widzisz, że to bzdura bo funkcja z jednym argumentem uint8_t dostaje go w rejestrze. Zamień to na.. a może wymyślisz sam?

Jeszcze raz: chcąc pisać w asemblerze musisz dobrze znać listę instrukcji - przejrzyj ją. Trudno, to kosztuje cały wieczór ale warto bo każda z nich "obsługuje" tylko pewien typ argumentów a niektóre robią ciekawe lub dziwne rzeczy. Zastanów się jak mógłbyś je wykorzystać. Przyjrzyj się różnicom między MOV, LDI, LDS itp. Zauważ też, że w AVR są wyraźne różnice w dostępności rejestrów R0-R15 i R16-R31. Nie wszystkie instrukcje mają dostęp do wszystkich. Pomyśl nad korzystaniem z par X, Y i Z oraz z mechanizmów autoin/dekrementacji adresów, itd... Plus tryby adresowania.

Zapamiętaj konwencję przekazywania parametrów i oddawania wartości do/z funkcji w AVR-GCC, bo za każdym razem będziesz się z tym zderzał.

itd.. bla bla bla..

marek1707, nie jestem ekspertem od asemblera AVR, ale ponieważ dla ARM-ów też wykorzystuję GNU Assembler, więc pozwolę się nie zgodzić z:

A w składni języka asemblera masz opisane jak tworzyć etykiety (miałeś właśnie na to zwrócić uwagę - coś zostało w głowie?) czyli rodzaj symboli. To muszą być ciągi znaków z określonego zestawu zaczynające się literą lub znakiem _. A Twoja etykieta składa się ze znaku 1 (jedynki). Rozumiem przepisywanie z książki, ale znając zasady nie wpisałbyś tu nigdy cyfry, przynajmniej nie na początku. Podejrzewam, że w oryginale była mała litera "L" czyli "l" (od słowa "label") wyglądająca w druku jak jedynka. Ciągi zaczynające się cyfrą są traktowane jak liczby a tu liczby wstawić nie możesz, to robi dopiero linker na etapie konsolidacji i lokowania kodu wynikowego

Tutaj znajdziesz więcej informacji: http://tigcc.ticalc.org/doc/gnuasm.html#SEC46

Numery mogą być etykietami - w odróżnieniu od napisów nie są wtedy unikatowe, stąd 1b oznacza skok to poprzedniej etykiety 1, a 1f - do kolejnej.

W przypadku ARM taka notacja działa na pewno, wydaje mi się że pod AVR będzie tak samo - w końcu w obu przypadkach mamy to samo narzędze.

Elvis: to nie jest to samo narzędzie. Dyrektywa asm jest przez (wspólny) kompilator GCC przetwarzana bezmyślnie tylko do etapu zastępowania symboli z procentami przez nazwy np. rejestrów, które sam uzna za słuszne. Ponieważ on nie rozumie instrukcji asemblerowych, dlatego każda użyta rzecz musi być opisana jako parametr na liście IN lub OUT - to tam mówisz kompilatorowi jak ma zastąpić dane miejsce tj. z czego może wybierać. Potem cały, rozwinięty tekst przesyłany jest do programu asemblera i tu już obowiązują reguły jak w czystym języku asemblera danego procesora. A te są w niuansach różne dla różnych procków. O ile wiem etykiety nie mogą tu zaczynać się od cyfr i tyle. Nie próbowałem tego wcześniej (nie przyszło mi to do głowy), ale nawet teraz nie umiem zmusić toolchaina do łyknięcia prawidłowego kodu z etykietą zamienioną na cyfry.

deshipu: jak pisałem nie mam tej książki i nie mogę odpowiadać za to co było w oryginale. Jeżeli ktoś po drodze zrobił z tamtejszego, poprawnego zapewne makra funkcję i to nam sprzedał nie mając pojęcia, że zmieniły się reguły przekazywania parametrów (a zmieniły się bo makro wie jak zostało wywołane - jest rozwijane przed właściwą kompilacją) to można tylko rozłożyć ręce. LDI musi mieć argument znany najpóźniej w trakcie linkowania a funkcja kolegi buchbuch tego nie spełnia.

Moim zdaniem temat będzie budził jeszcze wiele zamieszania, bo powinno się za to zabierać inaczej. Wstawka asm jako absolutnie niestandardowe rozszerzenie języka nie jest przecież polecanym rozwiązaniem - nawet w samych podręcznikach GCC. O niebo lepiej, szybciej i prościej pisze się kod asemblerowy w osobnych plikach. Nikt nie wymaga tam informowania kompilatora o żadnych INach, OUTach ani CLOBBERach, nie ma tego idiotycznego wstawiania cudzysłowów ani sztucznych znaczków nowych linii. Ważna jest owszem znajomość (przy współpracy z kodem w C) reguł przekazywania parametrów, ale jak widać przydaje się to zawsze. Ja robię takie wstawki w absolutnie tylko koniecznych miejscach (kilka NOP-ów gdy potrzebuję 0.5us czasu) a znajomość krzaczkowatego "extended asm' - jak nazywają to eufemistycznie w GCC uważam za prywatny sport wyczynowy.

Zaciekawiło mnie to czy lokalne etykiety są obsługiwane na avr. Jako próbę napisałem taki program:

void loop() {
 asm volatile (
    "   ldi r16, 100 \n\t"
    "1: nop \n\t "
    "   dec r16 \n\t"
    "   brne 1b \n\t"
    : : : "r16");
}

Z lenistwa jak widać jest to pod Arduino - ale gcc w końcu ten sam. U mnie kompiluje się bez najmniejszych problemów.

A niech to motyla noga, Elvis, masz rację. Do etykiety lokalnej trzeba odwołać się z literką b/f. To dlatego u mnie nie działało. No i jest to jednak specyfika narzędzi GNU. W kanonicznym podręczniku języka asemblera AVR (ATMEL) nie ma o tym ani słowa. Gdzie znajdę formalny opis tego mechanizmu? W "Using the GNU Compiler Collection" jest co prawda rozdział "Locally Declared Labels", ale dotyczy on C i wygląda to zupełnie inaczej. Dobra, znalazłem:

https://sourceware.org/binutils/docs/as/Symbol-Names.html

ale już widzę, że tego nie polubię. Naturalnym wydaje mi się wymyślanie sensownych i coś znaczących nazw a poszukiwanie gdzie ostatnio w kodzie użyłem etykiety "2:" bo do którejś z nich mam skok "JMP 2b" wydaje mi się słabe. Choć.. do przeskakiwania kilku instrukcji lub zamknięcia króciutkiej pętelki może zadziałać. No nic, trzeba będzie kiedyś sprawdzić w boju 🙂

Cacy panowie ale co z tymi operandami wyjściowymi? Może zamieśćcie kawałek działającego u was kodu do sprawdzenia u mnie bo może problem leży po stronie konfiguracji środowiska a nie w składni. Tak jak pisałem próbowałem na rozmaite sposoby w tym z modyfikacją zaproponowaną przez marka i za każdym razem rozbijam się o to samo.

Może przepisz przykład z książki - tylko dokładnie i bez literówek. Raczej autor wiedział co robi i sprawdził czy program działa.

Przeszło mi to przez myśl już wcześniej i tak też zrobiłem, efekt tego jest taki jak napisałem czyli wstawki bez operandów wyjściowych działają bez problemu z kolei podczas używania operandów wyjściowych kod się nie kompiluje wyrzucając za każdym razem podobne błędy. Dodam jeszcze, że programy pisane w całości w asm działają bez problemów. Jako, że nie jestem w stanie ustalić przyczyny temat zarzucam bo szkoda mi na niego czasu. Tak czy inaczej dzięki za zainteresowanie i próby rozwiązania tego problemu. Pozdrawiam.

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...