Skocz do zawartości

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


buchbuch

Pomocna odpowiedź

Witajcie. Od jakiegoś czasu zmagam się z problemem pogodzenia C z asemblerem z dość marnym skutkiem. Wsady do uC piszę w WinAvr zupgradowanym za pomocą atmel Toolchain. W necie znalazłem różne przykłady takiego miksowanie kodu niestety nie wszystko działa tak jak powinno. Postanowiłem jednak na podstawie dość popularnego kodu z dokumentu atmela avr305 służacego do realizacji komunikacji po rs dla uC nie posiadających sprzętowego UARTA popróbować zaimplementować to rozwiązanie we własnym programie.

Niestety moja wiedza na temat asemblera oscyluje wokół asm volatile ("nop \n\t"); 😉

ewentualnie jeszcze:

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

Dlatego mam kilka pytań takich jak np.:

1. w jaki sposób odnieść się w C do zmiennych (rejestrów) utworzonych w asemblerze?

.def	bitcnt	=R16
ldi 	bitcnt,9

i jak ją teraz użyć w c? przez adres pamięci? Jeśli tak to proszę o krótki przykład.

Kwestię wywoływania funkcji zrozumiałem - trzeba napisać funkcję c ze wstawką wywołującą funkcję w asm ale do zawartości rejestrów zmiennych jakoś nie mogę się dostać.

2. Zauważyłem też że autor tego kodu przepisuje rejestry jeden do drugiego bezpośrednio:

mov	Txbyte,Rxbyte

Tym czasem wykonując taką operację w C kompilator przepisuje wartości za pośrednictwem SRAM bez różnicy na wybrany stopień optymalizacji.

tekst z pliku .lss :

Rxbyte = Txbyte;
 b8:	80 91 61 00 	lds	r24, 0x0061	; 0x800061 <Txbyte>
 bc:	80 93 60 00 	sts	0x0060, r24	; 0x800060 <_edata>

Dlaczgo tak?

I jeszcze jedno pytanie dot. sposobu wysyłania danych przez rs.

3. Dlaczego wysyłany bajt jest zanegowany przed wysyłką?

com	Txbyte		;Inverte everything

Czyli w C: Txbyte = 0xFF - Txbyte;

Czy jest to podyktowane specyfiką standardu? Nie mam pół pojęcia jakie to może znaczenie mieć, dla mnie zupełnie nielogiczne.

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

Nie strzelaj! Jestem początkujący! 😉

Link do komentarza
Share on other sites

Mam dwie proste rady:

1. Skoro nie radzisz sobie ze zrozumieniem nawet prostych operacji w asemblerze AVR, po prostu poznaj to: zarówno architekturę procesora jak i sam język. Bez płynnej znajomości "naturalnego środowiska" pracy kodu asemblerowego czyli wnętrza procesora będzie Ci ciężko używać instrukcji. A bez znajomości listy instrukcji nie będziesz rozumiał dlaczego jedne mają takie ograniczenia a inne inne. Architektury RISC mają swoje specyficzne ograniczenia ale też i fajne zalety. Do tego dochodzą tryby adresowania - to ważny temat i nie pomiń go. W AVR mamy kilka ciekawych a pewnych brakuje. Na początku będzie trudno, szczególnie gdy nie pisałeś nigdy wcześniej w żadnym innym asemblerze, ale AVR to prosta architektura więc po kilku dniach będziesz z nią za pan brat 🙂 Polecam:

http://www.atmel.com/images/Atmel-0856-AVR-Instruction-Set-Manual.pdf

Uważne przejrzenie tego dokumentu pozwoli Ci zorientować się czym dysponujesz. Do tego porównanie z listą instrukcji konkretnie Twojego procesorka pokaże co ten potrafi a czego nie. Bo nie każdy ma zaimplementowaną pełną listę instrukcji. Zawsze trzeba rzucić okiem na tabelkę pod koniec karty katalogowej danego typu. Tam też (ale zwykle gdzieś na początku) jest opisana architektura: ile pamięci, gdzie się zaczyna, gdzie kończy, jak jest zrobiony stos, jakie rejestry I/O itd.

2. A teraz GCC vs asembler. Są tu dwa aspekty: łączenie plików C w jednym projekcie z plikami asemblerowymi i wstawki asemblerowe. W pierwszym przypadku bardzo, a drugim trochę przydaje się coś pt. konwencja przekazywania argumentów. Każdy kompilator ma swój ustandaryzowany sposób przekazywania argumentów do funkcji w C. Dlatego w jednym miejscu możesz napisać funkcję:

uint32_t moja_wielka_funkcja(char* bufor, uint8_t max_dlugosc, typ_tablica_komend* cmd_table)

{

}

a w drugim ją wywołać i jedno będzie pasowało do drugiego. Właśnie dzięki temu, że kompilator mając listę parametrów formalnych stosuje pewne reguły dostępu do nich w ciele funkcji a podczas rozwijania kodu w miejscu jej wywołania umieszcza parametry z listy aktualnej w odpowiednich miejscach. W przypadku GCC to zwykle są rejestry - dzięki temu że AVRy mają ich dużo, ale nie zawsze. Dzięki znajomości tych reguł możesz optymalizować sposoby pisania i wywoływania funkcji a wiedza jak i gdzie funkcja otrzymała argumenty pozwala na dostęp do nich we wstawkach asemblerowych.

Właśnie, wstawki: to jest rozszerzenie języka C i jako takie jest bardzo dokładnie opisane w podręczniku do avr-gcc. Może zacznij od tego, jest dość przystępne:

http://nongnu.org/avr-libc/user-manual/pages.html

W rozdziałach "avr-libc and assembler programs" oraz "Inline Assembler Cookbook" znajdziesz na pewno mnóstwo potrzebnej Ci wiedzy. Nie ma sensu jej tu przepisywać.

EDIT: A swoją drogą jaki jest sens przyspieszania funkcji emulacji UARTa? Przecież i tak musisz zużyć pewien czas żeby timing machania portami był zgodny z prędkością transmisji. Już rozumiem jakaś arytmetyka do przetwarzania sygnałów, operacje graficzne, generowanie sygnałów video czy ciekawe sortowanie dużych tablic itp ale.. UART?

Link do komentarza
Share on other sites

Dzięki za obszerną odpowiedź.

User_manual już przeglądałem. Trochę zrozumiałem, resztę mam zamiar zrozumieć 🙂

Do rozszyfrowywania assemblera korzystam właśnie z tej tabelki na końcu DS. Tak czy inaczej wolałbym zobaczyć jakiś prosty przykład obrazujący zagadnienia z którymi się zmagam niż teoretyzować ( i brnąć przez tą odwrotność odwrotności wyrażaną odwrotnie jaką w moim mniemaniu jest assembler i dlatego też jak przypuszczam powstał język C aby nie narażać ludzi na schizofremię.)

Już starożytni odkryli, że nauka za pomocą przykładów jest efektywniejsza niż sama teoria.

W teoretyczny sposób za to wytłumaczyłem sobie p. 2 : chodzi zapewne m.in o kontrolę pamięci podczas kompilacji bo inaczej nie można by było wykryć błędów, jeśli się mylę to mnie poprawcie.

Do rozstrzygnięcia pozostaje nadal p1 i p3 tak więc o ile to możliwe proszę o konkretne odpowiedzi z konkretnymi przykładami w szczególności odnośnie p1 bo p3 nie wymaga przykładu tylko prostej, zwięzłej, konkretnej i rzeczowej odpowiedzi, którą będę mógł sobie samodzielnie interpretować teoretycznie tak ogólnie jak szczególnie i w razie co to dopytam jeszcze bo w końcu po to jest forum.

Dzięki za zainteresowanie i pozdrawiam.

EDIT: Z całym szacunkiem marku ale Twoją odpowiedź w większej części można by porównać z tłumaczeniem początkującemu w elektronice żeby najpierw wykuł na pamieć oba tomy sztuki elektroniki i dopiero jak wszystko bezbłędnie zrozumie zabierał się za projektowanie dzielnika napięcia aby nie uszkodzić rezystora a o uC niech zacznie myśleć dopiero jak będzie w stanie samodzielnie zaprojektować i uruchomić rdzeń zbudowany z tranzystorów... To nie jest złośliwość tylko tak właśnie może być interpretowana tego typu odpowiedź i chciałbym żebyś miał tego świadomość.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Niewiele tam tego ale to jest właśnie konkretna odpowiedź za którą dziękuję 🙂

EDIT: konieczne sprostowanie: całkiem niemało tam "tego" 🙂 jeszcze raz dzięki i kermitowi również .

EDIT2: Przejrzałem cały kurs z uwagą. Na początek bardzo przydatny muszę stwierdzić. W szczególności komentarze w kodzie programów. Niestety jednak odpowiedzi na pytania w nim nie znalazłem. Pozostaje długotrwała kombinatoryka stosowana osobiście.. No chyba, że któryś z szacownych kolegów posiadających doświadczenie w asmie podzieli się malutką próbką swojej wiedzy i poda prosty przykład dotyczący pytania nr 1 z pierwszego postu. Oszczędziło by mi to sporo czasu.

Dzięki

Link do komentarza
Share on other sites

Ojej, już zapomniałem że z Tobą to jak z betonową ścianą rozmawiać trzeba. Przykładów nie chce mi się akurat dla Ciebie pisać - jakoś to przeżyjesz i będziesz musiał poszukać samodzielenie albo liczyć na łaskę innych Kolegów. Jak rozumiem nie zajrzałeś nawet do wskazanych rozdziałów opisu kompilatora avr-gcc albo biblioteki libc traktujących dokładnie o zagnieżdżaniu asemblera w C. Tam jest dużo przykładów pokazujących to o co pytasz, m.in. dostęp do zmiennych. A pytasz o dwie zupełnie różne rzeczy. Jedną jest składnia wstawek asemblerowych w C (bo jest inna niż czystego kodu źródłowego w pliku .a i musi zawierać więcej informacji dla kompilatora gdyż ten "obudowuje" Twój kod i musi się do niego dopasować) a drugą samo pisanie programów, czyli sposób wyrażania pewnych abstrakcyjnych algorytmów w języku asemblera AVR. Do pierwszej polecam właśnie opisy kompilatora C (bo to są jego wymagania - wszystko masz tam na tacy) a do drugiej obowiązkowo listę instrukcji i ew. dużo przykładowych, pełnych programów. Przecież nic byś w C nie napisał nie znając składni ani nie rozumiejąc działania if, switch czy while, prawda? Tu jest tak samo. Musisz wiedzieć czym się różni LSR od ASR, jak zrobić dodawanie stałej do rejestru lub porównać dwie liczby 16-bitowe.

Pkt 2: właśnie dlatego sugerowałem, abyś przejrzał pełną listę instrukcji. Dopiero tam widać co procesor może, bo na całej stronie masz wszystkie szczegóły jednej instrukcji. Ten skrócik w datasheet nie jest wiele wart dla osoby nie mającej pojęcia w temacie, czyli - jak się domyślasz - dla Ciebie. Co z resztą widać na załączonym obrazku: nie odróżniasz przesłania pamięć-pamięć od rejestr-rejestr. W RISC to pierwsze jest praktycznie niemożliwe i niespotykane, bo architektura typu load-store tego nie umie. Na liście AVR zarezerwowano ponad 1000 kodów operacyjnych na przesłania międzyrejestrowe, ale nie ma prostej instrukcji znanej choćby z 8051:

MOV komorka1,komorka2

No, to dlatego.

I nie pleć proszę o rzeczach o których nie masz zielonego pojęcia. Kontrola pamięci? W tym procesorze? Podczas kompilacji? Bzdura goni bzdurę.

Pytanie 3 to już chyba czyste lenistwo. Gdybyś rzucił okiem na algorytm pracy tego nadajnika (Figure 3-1 w AVR305) to od razu byś zauważył, że negują bajt bo za chwilę w pętli nadawania ustawiają w stan 1 linię TxD gdy C=0 a zerują TxD gdy C=1. Dzięki temu bity nadawane są w postaci prostej a magia siedzi w zachowaniu flagi C. Czy to wystarczy? A już sam się domyśl dlaczego tak robią - potraktuj to jak zadanie domowe. Wystarczy, że przejrzysz listę używanych tu instrukcji. Czy Ci się to podoba czy nie, pisanie w języku asemblera pełne jest takich "sztuczek". Niestety w każdym procesorze innych. Po jednym większym programie będziesz już coś o tym wiedział.

Acha, nie odpowiedziałeś na pytanie dlaczego i pod jakim względem optymalizujesz kod UARTa. Szczerze mówiąc nie spodziewam się wiele...

Moich odpowiedzi nie piszę dla zapełniania Forum, aż tak głupi nie jestem i nie mam tyle czasu. Widziałem, że zabrałeś się za poważne rzeczy nie mając żadnych podstaw. Przecież totalnego ignoranta poznaje się po dwóch zdaniach a Ty dałeś nam sporą próbkę. Reprezentujesz poziom chłopca budującego wzmacniacz pomiarowy lub nadajnik i pytającego o wartość opornika - dlaczego akurat taki. I tu są dwie możliwości: albo powiedzieć "bo tak, kiedyś zrozumiesz" albo wskazać książki, dużo książek o projektowaniu elektroniki. To nie jest złośliwość i chciałbym żebyś miał tego świadomość. A ponieważ czuję, że jesteś starszy niż większość tu osób, to uznałem że będziesz umiał docenić to drugie, systematyczne podejście i wskazane źródła informacji. Wystarczy poczytać w skupieniu i wiele spraw się rozjaśnia. Niestety myliłem się. Jedyny plus jaki osobiście wyniosłem z tego podejścia jest taki, że następnym razem pięć razy się zastanowię zanim włączę się do dyskusji z Tobą. Powodzenia w "samodzielnym interpretowaniu teoretycznym tak ogólnym jak szczególnym", cokolwiek to znaczy. Oczywiście: z całym szacunkiem itd..

  • Lubię! 1
Link do komentarza
Share on other sites

marek1707, ad3 masz zupełną rację ale wystarczyło zwykłe "tak", ad2 w większej części też masz rację. Czytałem, przemyślałem i w końcu zapomniałem a przecież niedawno czytałem też "RS 232C" Andrzeja Daniluka.. a co do wstawek assemblerowych to zmotywowała mnie książka "Mikrokontrolery AVR. Programowanie w języku C" Andrzeja Witkowskiego. Tak to jest kiedy zbyt wiele na głowie w tym samym czasie a mówiąc językiem technicznym: jak sie ma za bardzo naSRAMowane a za mało się FLASHuje.

Acha, nie odpowiedziałeś na pytanie dlaczego i pod jakim względem optymalizujesz kod UARTa. Szczerze mówiąc nie spodziewam się wiele...

Więc odpowiadam: w celu edukacyjnym i eksperymentalnym a co do spodziewania się po tym czegokolwiek cytuję tabelę z avr305:

BaudRate: 115200

Fosc: 4MHz

Error %: 0.8

Wystarczy poczytać w skupieniu i wiele spraw się rozjaśnia

Z tym też się zgodzę, niestety ostatnim (długim) czasem mam zupełny deficyt skupienia 🙁

Formę Twojej wypowiedzi w części "wycieczek osobistych" pozostawię bez komentarza. Na szczęście świadczy tylko o Tobie. BTW nice try, beer for you!

Link do komentarza
Share on other sites

Dzięki temu bity nadawane są w postaci prostej a magia siedzi w zachowaniu flagi C. Czy to wystarczy? A już sam się domyśl dlaczego tak robią - potraktuj to jak zadanie domowe.

C - carry flag, czyli flaga przepełnienia rejestru. Zapala się kiedy do rejestru ładowana jest większa ilość danych niż może on pomieścić. (pisane z pamięci) a robią tak w celu minimalizowania ilości operacji wykorzystując ją niejako przy okazji przesuwania danych. Zgadza się?

Czy Ci się to podoba czy nie, pisanie w języku asemblera pełne jest takich "sztuczek"
Nie tylko mi się to podoba, myślę nawet, że takie sztuczki nadają "smak" programowaniu.
Link do komentarza
Share on other sites

No i tego właśnie nie rozumiem. Przecież nie zadaję Ci pytania bo jestem ciekaw odpowiedzi, tylko żebyś sięgnął do źródeł. Ja wiem, że nie wiesz - widzę po tym co piszesz, inaczej od razu byś zrozumiał algorytm nadawania. Więc po co to wrzucasz po nocy takie rzeczy? Żeby się pochwalić? Czym? Brakami? Coś udowodnić? Komu? Żeby kogoś poinformować? No to patrz co wyszło: dodałeś do ogólnego bałaganu sieciowego kolejną pseudowiedzę - zadowolony? Takich standardów oczekujesz na tym Forum? Aby dobrze edukować ludzi na poziomie x trzeba samemu posiąść wiedzę i doświadczenie na poziomie 5x - to się nazywa autorytet. Więc gdy następnym razem będziesz miał ochotę pokazać światu na co Cię stać "z marszu", przypomnij sobie zdanie (nie moje):

"Lepiej jest nie odzywać się wcale i wydawać się głupim, niż odezwać się i rozwiać wszelkie wątpliwości"

(czyje to?) i weź sobie na wstrzymanie - to nie boli. Godziny tu nie robią, to nie wyścig. Gdybyś napisał dziś rano (także piszę z głowy, ale ograniczę się do jednego akapitu):

C to przerzutnik/flaga przeniesienia (ang. Carry). Pierwotnie był 9 bitem wyniku 8-bitowego arytmometru procesora. Tak więc jego podstawową funkcją jest pamiętanie przeniesienia z 8 bitu przy dodawaniu i pożyczki przy odejmowaniu. To pozwala na łatwą implementację arytmetyk wyższych precyzji. Uwaga: o ile podczas operacji na bajtach bez znaku C jest równoznaczne z nadmiarem (przepełnieniem) operacji 8-bitowej (wynik > 255) o tyle w arytmetyce ze znakiem C nie jest nadmiarem - do tego jest osobna flaga w słowie stanu procesora (V). Potem dodano ciekawe funkcje C związane z instrukcjami obracania cyklicznego i przesunięć - warto się temu przyjrzeć, bo są to fajne operacje niedostępne np. w języku (nomen-omen) C właśnie z uwagi na brak dostępu do flagi C.

to wyglądałoby zupełnie inaczej, prawda? Albo chociaż przepisał formułkę z jakiej książki. Kurcze, ale gość, poszukał, doczytał i wie coś zupełnie magicznego dla większości zjadaczy Arduino, wow. A tak? Bida. Może chciałeś dobrze (tylko co właściwie?) a wyszło jak zwykle.. Przy okazji: zastanów się jak generują bity stopu - bo tu kryje się kolejna przyczyna takiego a nie innego sterowania linią wyjściową TxD.

Wiedziałbyś też, że ładowanie nie ma tu nic do rzeczy. Co więcej, żadne instrukcje przesyłania/ładowania/składowania danych MOV/LD/ST itp nie zmieniają C właśnie po to, byś mógł podtrzymać jego stan od poprzedniej do następnej operacji arytmetycznej. A poza tym jak sobie wyobrażasz ładowanie do rejestru "większej ilości danych niż może on pomieścić"? Na próbę zastanów się (najpierw) i napisz (to potem) jak zrobić dodawanie lub porównywanie dwóch 16- lub 32-bitowych liczb umieszczonych dla uproszczenia w rejestrach procesora mając 8-bitowy arytmometr w 8-bitowym procesorze - to właśnie jest esencja używania flagi C. I to nie jest zadanie na czas.

O sztuczkach: może to i fajne, ale właśnie to oraz kompletny brak przenośności spowodowało szybki wzrost popularności języków wysokiego poziomu. Dzięki coraz lepszym kompilatorom i rozbudowanej optymalizacji coraz trudniej z nimi konkurować w pisaniu efektywnego kodu. A coraz szybsze i większe maszyny w ogóle zniechęcają do pochylania się nad językiem asemblera. Prosty przykład: nawet bardzo mały procesorek ma dziś 1K FLASHa. Żeby zapełnić to sensownym i przetestowanym programem w języku asemblera trzeba zużyć - wg. ogólnie obowiązujących w branży norm - ok. 1.5-2 miesiące. No to w tydzień-dwa piszę to samo w C (100-150 linii kodu?), program ma nawet i 2K, biorę procesorek tej samej rodziny ale oczko większy i kosztujący czasem tyle samo i.. zrobione. A Twoja wiedza asemblerowa przydaje się w bardzo wyjątkowych sytuacjach np. portowanie systemu operacyjnego na inną platformę lub żyłowanie pewnych operacji czasowych. Jest cenna, bo wtedy jesteś niezastąpiony, ale coraz rzadziej będziesz wykorzystywany i coraz dłużej będziesz czekał na swoją szansę. Niestety w Twoim przypadku żeby zrobić coś dużego i sensownego co będzie konkurencyjne do kodu kompilatora będziesz musiał włożyć w to naprawdę dużo pracy. A przykłady których tak się domagasz nie wystarczą...

Link do komentarza
Share on other sites

A czy czasem w tym konkretnym przypadku ta flaga nie jest używana właśnie w taki sposób jak napisałem?

.equ		sb	=1		

putchar:	ldi	bitcnt,9+sb	
	com	Txbyte		
	sec			

putchar0:	brcc	putchar1	
	cbi	PORTD,TxD	
	rjmp	putchar2		

putchar1:	sbi	PORTD,TxD	    
	nop

putchar2:	rcall UART_delay	
	rcall UART_delay

	lsr	Txbyte		
	dec	bitcnt		
	brne	putchar0	

	ret			

Najpierw rejestr jest negowany [com] następnie flaga C jest ręcznie zapalana [sec] aby wysłać bit startu ... rejestr jest przesuwany w prawo i w zależności od tego jaki bit "wystaje" poza rejestr flaga jest gaszona lub zapalana co z kolei skutkuje ustawieniem pinu TxD w odpowiedni sposób czyli zanegowany w stosunku do wartości przesuwanego bitu w rejestrze, za każdym razem też dekrementowany jest licznik bitów i sprawdzany warunek !(bitcnt == 0) jeśli wynik jest prawdziwy następuje skok do [putchar0] i tak aż do wyzerowania bitcnt czyli wysłania ostatniego bitu stopu. Bity stopu natomiast są wysyłane w momencie kiedy rejestr Txbyte zawiera już same zera a ich liczba zależy od zawartości rejestru bitcnt.

Teraz lepiej?

Wiedziałbyś też, że ładowanie nie ma tu nic do rzeczy.

Być może niefachowo się wyraziłem bo nie jestem fachowcem. Myślę jednak, że krótkie sprostowanie mojego niefachowego wyrażenia w zupełności by wystarczyło i jeśli czyta ten wątek ktoś kogo być może to interesuje choćby tylko poglądowo to zrozumiałby więcej na podstawie takiego sprostowania niż całego referatu nafaszerowanego wyjazdami osobistymi.

Przypominasz mi trochę nauczyciela mechaniki jeszcze ze szkoły średniej który, dusił wszystkich swoim autorytetem wymagając wiedzy którą zapewne miał ale której nie umiał przekazać u którego można było liczyć maksymalnie na 3 nawet kiedy praca była pisana bezbłędnie. Zniechęcał tylko do nauki zamiast motywować. Po co komu taki nauczyciel? Biorąc pod uwagę całokształt konwersacji z Tobą odnoszę wrażenie, że taki masz właśnie cel. Co to za różnica w jakim celu chcę się tego uczyć? Chcę i już czy ma to wg. Ciebie sens czy nie 🙂

EDIT: odp. na pierwsze pytanie:

register uint8_t variable_in_c asm("r17");
variable_in_c = 0;

Link do komentarza
Share on other sites

Całe szczęście, że nam płacisz za uczenie ciebie i możesz stawiać wymagania jak to ma wyglądać, bo inaczej to straszny wstyd by był, że ludzie poświęcają swój czas i pomagają ci, a ty im mówisz, że robią to źle i mają się bardziej starać.

Link do komentarza
Share on other sites

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

Od samego początku mojej aktywności na tym forum strasznie uprzykrzasz, czasami aż mdli... Zadałem kilka prostych pytań oczekując prostych odpowiedzi, nic więcej...

Link do komentarza
Share on other sites

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...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.