logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Aktualizacja kompilatora CodeSourcery dla ARM - co nowego?

michalko12 09 Maj 2010 16:27 3008 27
REKLAMA
  • REKLAMA
  • #2 8069207
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    W tej wersji natrafiłem na problemy z sprintf w połączeniu z floatami.
    Przy optymalizacji -O2 nie zawsze chce działać, po przestawieniu na -Os ruszyło.

    Może komuś uda się rozgryźć problem. Mnie na razie -Os odpowiada więc nie wgłębiam się.
  • Pomocny post
    #3 8069520
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    A masz wyrównane stosy do 8-miu bajtów? Kiedyś o tym pisałem - bez tego właśnie CZASEM dzieją się problemy - np z funkcjami variadic, czyli m.in. z printfem...

    4\/3!!
  • REKLAMA
  • #4 8070188
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    A masz wyrównane stosy do 8-miu bajtów? Kiedyś o tym pisałem - bez tego właśnie CZASEM dzieją się problemy - np z funkcjami variadic, czyli m.in. z printfem...

    4\/3!!


    Sekcję .data mam wyrównaną do 8, ale to wszystko co mogę zrobić. Ze stosami jest problem ponieważ korzystam z FreeRTOSa i każdy task ma dynamicznie przydzielany swój stos ze sterty, wiec tutaj raczej nie to powinno być problemem. Z resztą kompilator sam powinien dbać o to, aby zmienne dla funkcji przekazywane poprzez stos wyrównywać w zależności od architektury, w przypadku CM3 to teoretycznie nie powinno mieć znaczenie dla samego działania, ewentualnie może mieć wpływ na wydajność potoku. Tak mi się zdaje.
  • Pomocny post
    #5 8070365
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Niestety - ma gigantyczny wpływ... takie jest wymaganie standardu ARM EABI. Stos musi być w każdym momencie wyrównany do 8 - takie życie z ARMami.

    4\/3!!
  • #6 8070471
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Może i ma sens to o czym mówisz, ale i tak nie wiem jak przebrnąć przez to w przypadku stosu przydzielanego dynamicznie, prawdopodobnie musiałbym w źródłach RTOSa i malloca pogrzebać lub stworzyć oddzielną funkcje do przydzielania stosu. Objawy są właśnie takie jakby parametry były źle do funkcji przekazywane, wszystko działa, a sprintf wyświetla bzdury.
  • #7 8070484
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Jeśli funkcje tego nie robią "same", to są niezgodne ze standardem - przejrzyj te źródła to zobaczysz czy jest tam jakieś magiczne wyrównywanie czy nie... Jeśli nie, to wtedy być może warto zgłosić jakiegoś buga.

    Anyway - jak masz debugger, to możesz sprawdzić jaki jest adres stosu w momencie wywoływania sprintfa (tylko PRZED ew. ładowaniem argumentów na ten stos).

    4\/3!!
  • #8 8070523
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Jednak w mallocu jest taka opcja i była ustawiona na 4, wieczorem sprawdzę czy to coś pomogło.
  • #9 8070531
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Zmiana w samym mallocu może nie pomóc - zależy jaki będzie najwyższy adres "stosu" dostępny dla wątku, a malloc pewnie wyrównuje początek...

    4\/3!!
  • #10 8070562
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    Zmiana w samym mallocu może nie pomóc - zależy jaki będzie najwyższy adres "stosu" dostępny dla wątku, a malloc pewnie wyrównuje początek...
    4\/3!!

    Mówiąc najwyższy rozumiem jako aktualny wierzchołek stosu, bo właśnie to mnie najbardziej zastanawia cały czas, co ma wyrównanie początku stosu do aktualnego miejsca na stosie gdzie wskaźnik stosu ma zamaskowane tylko 2 najmłodsze bity?
  • #11 8070637
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Istotny oczywiście jest SP. Wyrównanie w funkcji malloc dotyczy na 99% adresu "od dołu", a Ciebie właśnie interesuje to co będzie "na górze".

    No i nikt nie mówił o wyrównywaniu "początku" stosu [;

    4\/3!!
  • #12 8070819
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    Istotny oczywiście jest SP. Wyrównanie w funkcji malloc dotyczy na 99% adresu "od dołu", a Ciebie właśnie interesuje to co będzie "na górze".

    No i nikt nie mówił o wyrównywaniu "początku" stosu [;

    4\/3!!


    Freddie Chopin napisał:
    A masz wyrównane stosy do 8-miu bajtów?


    Jakbyś napisał wyrównanie w stosie do 8 to bym właściwie to zrozumiał ;) , a tak przyjąłem że chodzi ci o stosy dla obu trybów rdzenia.

    W takim wypadku niewiele da się zrobić bo to kompilator może tylko o to dbać żeby było wyrównanie do 8 przy wszelkich operacjach na stosie, ale wątpię w to żeby to o to chodziło. Jakaś biblioteka raczej jest skopana bo dopóki nie ma w sprintfie wyświetlania floata to wszystko dobrze działa. Jeszcze nie sprawdziłem czy jakbym zamienił kolejność wyświetlanych typów czy by to cos zmieniło.
  • REKLAMA
  • Pomocny post
    #13 8071088
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Raczej nic to nie zmieni. W funkcjach variadic float jest traktowany jak double (zajmuje 8 bajtów lub 2 rejestry), stąd problem. Kompilator dba o stos podczas pracy programu, ale to co dostaje na wejściu musi być wyrównane do 8-miu bajtów.

    Jeśli chcesz znaleźć źródło problemów, to olej na chwilę FreeRTOSa i po prostu w jakimś programiku zwykłym przetestuj tego sprintfa wyrównując stos do 8, a potem do 4.

    Gdy miałem stosy wyrównane do 4, to również działy się cuda - wyświetlało przeważnie 0, czasem jakieś bezsensowne liczby, zmiana abstrakcyjnych opcji kompilacji powodowała poprawę, ale tylko do czasu aż znów było źle. Dopiero wyrównanie stosu do 8 rozwiązało problem w 100%.

    Mam zainstalowany ten najnowszy kompilator i u mnie sprintf z wykorzystaniem float działa tak jak działał. Szczerze mówiąc pomiędzy obecną a poprzednią wersją różnica jest baaaaaardzo mała (wersje głównych narzędzie pozostały takie same) - głównie są to poprawki jakichś bugów.

    4\/3!!
  • #14 8071952
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    Raczej nic to nie zmieni. W funkcjach variadic float jest traktowany jak double (zajmuje 8 bajtów lub 2 rejestry), stąd problem. Kompilator dba o stos podczas pracy programu, ale to co dostaje na wejściu musi być wyrównane do 8-miu bajtów.

    Jeśli chcesz znaleźć źródło problemów, to olej na chwilę FreeRTOSa i po prostu w jakimś programiku zwykłym przetestuj tego sprintfa wyrównując stos do 8, a potem do 4.

    Gdy miałem stosy wyrównane do 4, to również działy się cuda - wyświetlało przeważnie 0, czasem jakieś bezsensowne liczby, zmiana abstrakcyjnych opcji kompilacji powodowała poprawę, ale tylko do czasu aż znów było źle. Dopiero wyrównanie stosu do 8 rozwiązało problem w 100%.

    Mam zainstalowany ten najnowszy kompilator i u mnie sprintf z wykorzystaniem float działa tak jak działał. Szczerze mówiąc pomiędzy obecną a poprzednią wersją różnica jest baaaaaardzo mała (wersje głównych narzędzie pozostały takie same) - głównie są to poprawki jakichś bugów.

    4\/3!!


    Co rozumiesz przez pojęcie wyrównać stos do 8 lub 4? Cały obszar stosu mogę wyrównać ale jaki mam wpływ na odkładanie danych na stos? Wyrównanie całego obszaru stosu do 8 nic nie daje dalej mam ten błąd przy optymalizacji -O2.
    Zamiana miejscami typów zmiennych nie pozostaje bez echa. Przykłady:


    sprintf((char*)buf,"%s %u %6.2f",puch,uint,flt);


    Powoduje, że pierwsze dwie są dobrze wyświetlane a trzecia typu float jest wyświetlana jako -0.00

    sprintf((char*)buf,"%s %6.2f %u",puch,flt,uint);


    Powoduje, że pierwsza jest dobrze wyświetlana, a dwie kolejne źle i mało tego, za którymś tam razem (funkcja wywoływana co 200ms) sprintf znacznie sieje poza dostępny bufor znakami '0' - '9'


    sprintf((char*)buf,"%6.2f %s %u",flt ,puch,uint);


    Żadna dana nie jest dobrze wyświetlana.

    Nie wiem może mam coś źle w parametrach linkera albo czegoś brakuje ???
    Cytat:
    LINKER_FLAGS=-nostartfiles -Xlinker -oOutput.axf -Xlinker -M -Xlinker -Map=Output.map -Xlinker --gc-sections -Xlinker -lm
  • REKLAMA
  • #15 8072916
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    michalko12 napisał:
    Co rozumiesz przez pojęcie wyrównać stos do 8 lub 4?

    Wyrównać początkową wartość rejestru SP do wielokrotności 8.

    Cytat:
    Cały obszar stosu mogę wyrównać ale jaki mam wpływ na odkładanie danych na stos?

    Żaden, ale o to dba kompilator (zawsze rezerwuje na stosie wielokrotność liczby 8). Byle tylko na początku wartość była dobra.

    Cytat:
    Wyrównanie całego obszaru stosu do 8 nic nie daje dalej mam ten błąd przy optymalizacji -O2.

    Chyba muszę przetestować Twoje przykłady w wolnej chwili...

    4\/3!!
  • #16 8073067
    Konto nie istnieje
    Poziom 1  
  • #17 8073095
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Wyrównanie to musi być zrealizowane na poziomie startupu albo skryptu linkera

    W moich przykładach:

    .stack :
    	{
    		. = ALIGN(8);
    		__stack_start = .;
    		PROVIDE(__stack_start = __stack_start);
    
    		. = ALIGN(8);
    		__main_stack_start = .;
    		PROVIDE(__main_stack_start = __main_stack_start);
    
    		. += __main_stack_size;
    
    		. = ALIGN(8); // !!!! -=-=-=-=-=- TUTAJ -=-=-=-=-=- !!!!
    		__main_stack_end = .;
    		PROVIDE(__main_stack_end = __main_stack_end);
    
    		. = ALIGN(8);
    		__process_stack_start = .;
    		PROVIDE(__process_stack_start = __process_stack_start);
    
    		. += __process_stack_size;
    
    		. = ALIGN(8); // !!!! -=-=-=-=-=- TUTAJ -=-=-=-=-=- !!!!
    		__process_stack_end = .;
    		PROVIDE(__process_stack_end = __process_stack_end);
    
    		. = ALIGN(8);
    		__stack_end = .;
    		PROVIDE(__stack_end = __stack_end);
    	} > ram AT > ram


    4\/3!!
  • #18 8073154
    Konto nie istnieje
    Poziom 1  
  • #19 8102191
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Problemu z sprintf ciąg dalszy.

    Mam wrażenie że to nie z sprintfem są problemy tylko z funkcjami na danych zmiennoprzecinkowych, bo oprócz problemów z wyświetlaniem float'ów sa tez problemy z konwersją, nieważne czy jawna konwersja czy niejawna np:
    int = float lub int = (int)float;
    powodują że w zmiennej int znajduje się bitowy odpowiednik zmiennej float.
    taki sam efekt jest przy zastosowaniu funkcji long lroundf(float x);
    Czy jest możliwe że gcc podstawia jakieś lewe biblioteki???
  • #21 8102762
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    A masz wyrównane stosy do 8-miu bajtów? Kiedyś o tym pisałem - bez tego właśnie CZASEM dzieją się problemy - np z funkcjami variadic, czyli m.in. z printfem...


    Ufff...

    przed:
    static unsigned long  pulStack[STACK_SIZE];


    po:
    static unsigned long  pulStack[STACK_SIZE] __attribute__((aligned(8)));


    i po problemie (przynajmniej na razie).

    Nie sądziłem że ten stos może mieć coś z tym wspólnego. Sprintf wywoływany był z tasków które mają własne stosy. Jest jeszcze jeden główny stos z którego korzystają przerwania i RTOS. Stosy poszczególnych tasków były wyrównane do 8, ale nie główny stos i w tym był problem. W którym momencie ta funkcja korzysta z tego stosu to nie wiem. Wiem że straciłem kilka dni na wyleczeniu sprintfa!
    Dzięki Freddie Chopin!!!

    Dodano po 59 [minuty]:

    michalko12 napisał:

    i po problemie (przynajmniej na razie).

    I właśnie tyle trwało po problemie. :( O co tu chodzi??????
  • Pomocny post
    #22 8102996
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Posty: 13336
    Pomógł: 1712
    Ocena: 870
    Pamiętaj, że atrybutem aligned wyrównujesz najmłodszy adres stosu, a tobie zależy niejako na najstarszym, bo stos jest "descending". STACK_SIZE musi być podzielny przez 8, tak samo jak rozmia stosów dla wątków.

    4\/3!!
  • #23 8103168
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Freddie Chopin napisał:
    Pamiętaj, że atrybutem aligned wyrównujesz najmłodszy adres stosu, a tobie zależy niejako na najstarszym, bo stos jest "descending". STACK_SIZE musi być podzielny przez 8, tak samo jak rozmia stosów dla wątków.


    No o tym też zapomniałem i nie wziąłem pod uwagę, ale to akurat było dobrze. Znalazłem jeszcze jeden błąd tego typu w mallocu FreeRTOSa. Malloc jest dostarczony razem z RTOSem i wyrównanie alokacji bloków pamięci w stercie jest trzymane, ale wyrównanie samej sterty już nie! Grrrryyyyy...
    Wyrównanie sterty pomogło, ale czy na stałe to nie wiem, w każdym bądź razie widać że w tym jest problem.

    Dla pokoleń:

    Plik heap_2.c
    przed:
    static struct xRTOS_HEAP
    {
    	unsigned portLONG ulDummy;
    	unsigned portCHAR ucHeap[ configTOTAL_HEAP_SIZE ];
    } xHeap;


    po:
    static struct xRTOS_HEAP
    {
    	unsigned portCHAR ucHeap[ configTOTAL_HEAP_SIZE ];
    	unsigned portLONG ulDummy;
    } xHeap __attribute__((aligned(8)));


    Edit:

    W sumie niby został zastosowany mechanizm wyrównania poprzez wstawienie jako pierwszego nigdzie nieużywanego składnika unsigned portLONG ulDummy, ale to daje wyrównanie do 4 a nie do 8.
    Cytat:
    /* Allocate the memory for the heap. The struct is used to force byte
    alignment without using any non-portable code. */


    i tak naprawdę wystarcza tylko:

    static struct xRTOS_HEAP
    {
    	unsigned portCHAR ucHeap[ configTOTAL_HEAP_SIZE ];
    } xHeap __attribute__((aligned(8)));
  • #24 8703165
    aszewczyk
    Poziom 11  
    Posty: 45
    michalko12 napisał:

    Ufff...

    przed:
    static unsigned long  pulStack[STACK_SIZE];


    po:
    static unsigned long  pulStack[STACK_SIZE] __attribute__((aligned(8)));


    i po problemie (przynajmniej na razie).


    Nie moge znalezc tej funkcji ani zadnego jej odpowiednika w moim porcie:/
  • #25 8703490
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    To nie funkcja tylko tablica rezerwująca obszar dla głównego stosu i znajduje się w pliku startup. Ciebie to nie dotyczy ponieważ to jest port dla Cortexa a ty masz już w swoim startupie właściwie poustawiane stosy (info dla innych chodzi o STR912).
    W najnowszej wersji Freertosa (6.1.0) coś zmienili w definicji sterty ale to i tak chyba nie pomoże bo double dla tych procesorów to i tak 32 bity a nie 64.

    /* Allocate the memory for the heap.  The struct is used to force byte
    alignment without using any non-portable code. */
    static union xRTOS_HEAP
    {
    	#if portBYTE_ALIGNMENT == 8
    		volatile portDOUBLE dDummy;
    	#else
    		volatile unsigned long ulDummy;
    	#endif
    	unsigned char ucHeap[ configTOTAL_HEAP_SIZE ];
    } xHeap;
    
  • #26 8704176
    aszewczyk
    Poziom 11  
    Posty: 45
    No dobra. Heapa zmienilem tak jak napisales pare postow wyzej. Co do stacka znalazlem funkcje ktora ustawia go dla watkow w pliku thread.c ale nie wiem co pozmieniac. W startupie stack ma adres wierzcholka podzielny przez 8 tak samo jak i rozmiary dla kazdego trybu procesora podzielne przez 8.
  • #27 8784272
    aszewczyk
    Poziom 11  
    Posty: 45
    Przydalo by sie zebym to w koncu ruszyla ale dalej nie wiem co pozmieniac.
    Cytat:
    /* Constants required to setup the initial stack. */
    #define portINITIAL_SPSR ( ( portSTACK_TYPE ) 0x1f ) /* System mode, ARM mode, interrupts enabled. */
    #define portTHUMB_MODE_BIT ( ( portSTACK_TYPE ) 0x20 )
    #define portINSTRUCTION_SIZE ( ( portSTACK_TYPE ) 4 )


    Cytat:
    /**
    * Initialize the stack of a task to look exactly as if a call to
    * portSAVE_CONTEXT had been called. The registers have to be placed on the stack in
    * the order that the port expects to find them.
    *
    * @Param pxTopOfStack Pointer to the task stack.
    * @Param pxCode Pointer to the task control function.
    * @Param pvParameters Pointer to the parameter to be passed to the task entry function.
    * @return_ The new top of the stack.
    */
    portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack, pdTASK_CODE pxCode, void *pvParameters )
    {
    portSTACK_TYPE *pxOriginalTOS;

    pxOriginalTOS = pxTopOfStack;

    /* Setup the initial stack of the task. The stack is set exactly as
    expected by the portRESTORE_CONTEXT() macro. */

    /* First on the stack is the return address - which in this case is the
    start of the task. The offset is added to make the return address appear
    as it would within an IRQ ISR. */
    *pxTopOfStack = ( portSTACK_TYPE ) pxCode + portINSTRUCTION_SIZE;
    pxTopOfStack--;

    *pxTopOfStack = ( portSTACK_TYPE ) 0xaaaaaaaa; /* R14 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) pxOriginalTOS; /* Stack used when task starts goes in R13. */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x12121212; /* R12 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x11111111; /* R11 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x10101010; /* R10 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x09090909; /* R9 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x08080808; /* R8 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x07070707; /* R7 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x06060606; /* R6 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x05050505; /* R5 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x04040404; /* R4 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x03030303; /* R3 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x02020202; /* R2 */
    pxTopOfStack--;
    *pxTopOfStack = ( portSTACK_TYPE ) 0x01010101; /* R1 */
    pxTopOfStack--;

    /* When the task starts is will expect to find the function parameter in
    R0. */
    *pxTopOfStack = ( portSTACK_TYPE ) pvParameters; /* R0 */
    pxTopOfStack--;

    /* The status register is set for system mode, with interrupts enabled. */
    *pxTopOfStack = ( portSTACK_TYPE ) portINITIAL_SPSR;

    #ifdef THUMB_INTERWORK
    {
    /* We want the task to start in thumb mode. */
    *pxTopOfStack |= portTHUMB_MODE_BIT;
    }
    #endif

    pxTopOfStack--;

    /* Interrupt flags cannot always be stored on the stack and will
    instead be stored in a variable, which is then saved as part of the
    tasks context. */
    *pxTopOfStack = portNO_CRITICAL_NESTING;

    return pxTopOfStack;
    }
    /*-----------------------------------------------------------*/
  • #28 8785808
    august_a
    Poziom 21  
    Posty: 572
    Pomógł: 32
    Ocena: 4
    Witam,
    podłączę się do tematu:

    Podczas użycia w programie funkcji sprintf poraz 2 - program się wiesza.

    W czym może być problem?

    Procek SAM7X512.
    Pzdr.
    A

Podsumowanie tematu

✨ W dyskusji poruszono problemy związane z aktualizacją kompilatora CodeSourcery dla ARM, szczególnie dotyczące funkcji sprintf i obsługi typów zmiennoprzecinkowych. Użytkownicy zgłaszali trudności z wyświetlaniem wartości float, które były wynikiem niewłaściwego wyrównania stosu. Wskazano, że stos musi być wyrównany do 8 bajtów zgodnie z wymaganiami standardu ARM EABI. Problemy z sprintf były związane z dynamicznie przydzielanym stosem w FreeRTOS, co wymagało modyfikacji w kodzie, aby zapewnić odpowiednie wyrównanie. Użytkownicy dzielili się rozwiązaniami, w tym poprawkami w mallocu oraz atrybutami wyrównania w strukturach. Ostatecznie, poprawne wyrównanie stosu i sterty rozwiązało problemy z wyświetlaniem.
Wygenerowane przez model językowy.
REKLAMA