[Megoldva] Kioptimalizálódhat-e a függvényhívás?

Fórumok

Nézzük az alábbit:

#define INTERRUPT_test_and_run(PIE, PIR, MASK, FNC) (((PIE) & (PIR) & (MASK)) ?\
                                                        (FNC, true) : false)

void __interrupt() INTERRUPT_InterruptManager(void) {

    bool ready;

    ready = false;
    ready |= INTERRUPT_test_and_run(PIE3, PIR3, _PIR3_TMR0IF_MASK, tmr0_isr());
    ready |= INTERRUPT_test_and_run(PIE4, PIR4, _PIR4_U1RXIF_MASK, rx_isr());
    ready |= INTERRUPT_test_and_run(PIE4, PIR4, _PIR4_U1TXIF_MASK, tx_isr());
    if (!ready) RESET();                                                        // unhandled interrupt
}

Gondolhatja-e a fordító, hogy a RESET() hívásához szükséges ready változó helyes előállításához szükségtelen a makróban hivatkozott FNC meghívása? Mert ha ezt gondolja, akkor épp az interrupt handlert fogja kispórolni, és sovány vigasz lesz annak a néhány byte-nak a megspórolása. :) Veszélyes-e ebből a szempontból ez a makró?

Szerk.: Nem azt állítom, hogy most rosszul fordítja - még nem próbáltam ki -, hanem az a kérdés, hogy vajon ez egy annyira ügyetlen makró, amitől állandóan ott a fejem felett a pallos, hogy egyszer csak kispórolja a fordító az IT handler hívását, vagy ez így teljesen jó, netán teljesen rossz, vagy picit módosítani kellene rajta?

Megjegyzés:

Annak, aki esetleg érdekesnek gondolja, úgy tűnik, az eredeti makró - itt fentebb - hibás, s a fordító nem érzi szükségesnek a vessző operátor előtti függvény hívását, hiszen a kifejezés anélkül is kiértékelhető. Akkor viszont nem hívódik az interrupt handler.

Hozzászólások

Azt szerintem ki kell számítania, mert különben nem tudná a végén, hogy reset legyen, vagy sem. Itt az a bajom, hogy az (FNC, true) az az FNC hívása nélkül is true, tehát FNC hívása elhagyható a kifejezés eredményének szempontjából, viszont épp az FNC a katarzis ebben a dologban. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Igen, vessző operátor. Ezt láttam oda a legalkalmasabbnak. Balról jobbra, és a leggyengébben „köt”.

Szerk.: Csak ugye FNC nélkül is tudható, hogy (FNC, true) az true, tehát nem kell FNC-t hívni. Szóval sajnos ez így nem jó, már látom.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Attól tekintsünk el, hogy a ready változó neve kifejezőbb volna, ha átnevezném done-ra. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Részben megvan a válasz:

warning: (759) expression generates no code

Szomorú vagyok, át kell alakítani. Az előbb nem vettem észre, csak most. :(

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Ezt ki kellene vizsgálni, hogy pontosan mire mondja és miért.

Hacsak az nem, hogy a INTERRUPT_test_and_run első három paramétere fordítási időben ismert, ezért tudja a fordító, hogy a FNC mindig meghívódik, és a ready már háromszorosan nemnulla a függvény végére, tehát a RESET hívására sosincs szükség.

Ebben az a esetben viszont az a kérdés, hogy mi másnak kellene történnie?

Szerk: a vesszőoperátornak nem az a szemantikája, hogy 'hajts végre a vessző utáni részt', hanem az, hogy 'hajtsd végre a vessző előtti részt (mellékhatásokkal együtt), azután hajtsd végre a vessző utáni részt, a kimenet ennek a második résznek az eredménye'.

Szerk2: most talán jobban értem, hogy kifejezetten a vesszőoperátorra vonatkozott a kérdés. Szerencsére az előző kiegészítésben ezt is leírtam; most csak annyit tennék hozzá, hogy a függvényhívásnál a paramétereket elválasztó vessző nem vesszőoperátor, vagyis a paraméterek kiértékelése tetszőleges sorrendben történhet, akár futásonként változhat (tehát ne építsünk arra, hogy pl. az előző futásnál balról-jobbra volt).

Nem tudhatja. Ha engedélyezve van egy negyedik periféria, amelyik IT-t kér, akkor egyik maszkra sem lesz illeszkedés, és a végén a RESET()-nek kell futnia. Ha kiegyszerűsíthető lenne fordítási időben, nem írtam volna bele. Jó, tudom, rezeg a léc, mert alant NULL-ra vizsgálok, amikor sohasem hívom a függvényt NULL-al, de szerintem az azért úgy szép. :)

Igen, ez fontos kérdés. Tehát a vessző operátor esetén mindenképpen végre lesz hajtva a vessző mindkét oldalán található kifejezés? Akkor tehát mégis majdnem jó volt az eredeti makróm?

Igen, azt tudom, hogy függvényhívás esetén nem csinálhatom például ezt: fnc(x, x++); mert ennek csúnya vége lehet.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

> Ha engedélyezve van egy negyedik periféria, amelyik IT-t kér, akkor egyik maszkra sem lesz illeszkedés, és a végén a RESET()-nek kell futnia.

Off: Hát ezért lenne jó, ha az emberi olvasó meg tudná tippelné, hogy  a PIE, PIR, MASK szimbolumok melyike #define-olt konstants, és melyike változó.

> Tehát a vessző operátor esetén mindenképpen végre lesz hajtva a vessző mindkét oldalán található kifejezés? Akkor tehát mégis majdnem jó volt az eredeti makróm?

Most zavarba jöttem, milyen lenne a "nem mindenképpen"? Mitől függene, hogy megcsinálja-e? (Jó, ha olyan speciális eset, hogy látja a fordító, hogy nincs mellékhatása, akkor lehet belőle egy 'useless calcluation' warning, de a függvényhívás az nem ilyen [bár ha a függvény is ugyanabban a source-ben van, és mondjuk üres a törzse, akkor akár ez is mehet].)

A PIE az a regiszter abszolút címmel, amelyiknek az egyes bitjei egyes perifériák IT kérését engedélyezik. Azért kell makró, mert van egy rakás ilyen regiszter, hiszen 8 bites az MCU, de több, mint 8 IT-t kérő periféria van. A PIR az a regiszter, amelyben 1-be billen a PIE-vel azonos helyiértéken lévő bit, ha teljesül az IT kérés feltétele, például soros porton beérkezett egy karakter. Ez akkor is bebillen 1-be, ha amúgy a PIE által tiltva van az IT kérés, csak ez utóbbi esetben nem keletkezik megszakítás. Azért kell mégis összemaszkolni PIE-t és PIR-t, mert olyan is lehet, hogy egy másik periféria kér IT-t, amiatt megyünk bele ebbe az IT rutinba - itt csak egyetlen IT vektor van -, és ha PIE tiltottsága ellenére csak PIR-t vizsgálnánk, tévesen kiszolgálnánk az amúgy tiltott IT-jű perifériát.

A MASK pedig kiszedi azt a bitet, amelyik perifériához tartozik a PIE és PIR a nyolcból.

Zavarosan fogalmaztam. Olyasmire gondoltam, mint például az || vagy && operátorok esetében, hogy amikor már lehet tudni az eredményt, végre sem hajtódik a további része ezzel időt spórolva, de ezt például pointereknél vaskosan ki is használjuk:

char c, *p;

while (p && *p && *p != c) p++;

Tehát akkor a vessző operátor esetében mindkét oldal mindenképpen kiértékelésre, végrehajtásra kerül, ha jól értem.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Most csinaltam egy ilyesmit:

$ cat x.c
#include <stdio.h>

static int funct(int x)
{
 printf("x=%d\n",x);
 return(0);
}

int main(void)
{
 int    a;
 a=(funct(8),6*7);
 printf("a=%d\n",a);
 return(0);
}

$ gcc -Wall -pedantic -ansi -O3 -o x x.c
$ ./x
x=8
a=42
$ gcc --version
gcc (Debian 12.2.0-14) 12.2.0
$ 

Ezalapjan olyba' tunik hogy kiertekeli, akkor is hogyha nem hasznalja fel az eredmenyt. 

Köszönöm!

Akkor lehet, visszatérek a makrós megoldáshoz, bár a static inline függvény is majdnem makró, csak nem a preprocesszor helyettesít még szövegesen, hanem a fordító teszi az adott függvény törzsét a hívó helyre, ami kb. ugyanazt eredményezi szerencsés esetben.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Egyre kevésbé értem. Ha az a ready volatile, akkor nem ad a fordító warningot azzal, hogy a kifejezésből nem fordult kód, viszont byte-ra pontosan ugyanolyan hosszúságú kód fordul. Másfelől, ha nem is hívja FNC-t, a kifejezést szerintem nem spórolhatja le, ellenben honnan tudhatná, hogy a végén kell-e a RESET() hívása, vagy sem? Azt elhiszem, hogy FNC hívást kispórolja, de a kifejezést muszáj lenne kiértékelnie.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Én inkább azt szoktam bizonytalanság esetén, hogy megkerülöm a problémát, és egy robusztus, mindenképp működő alakban írom le. Szerintem az inline függvényes megoldásomat nem tudja elszúrni, mert akkor már tényleg nem azt csinálja, ami oda van írva.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Nem úgy van, hogy 0-ás bittől lefoglal annyit, amennyit kérsz, a következő field-del folytatja, és így tovább? Gondolom, ha a szószélességnél - pl. 8 vagy 32 bit - több esetén folytatja a következő 8 vagy 32 bites szavon, illetve szintén gondolom, hogy egy field belsejébe nem esik szóhatár.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Azért ha egy unsigned counter: 5; átlóg az architektúra bitszélességének határn, majd csinálsz egy var.counter++; utasítást, akkor abba megbolondul a fél világ, akkora kóddá fog fordulni. De optimális esetben is kivadássza a két címről a megfelelő biteket megfelelően összeillesztve, növeli, majd az eredmény megfelelő töredékeit az eredeti helyre pl. egy xor, and, xor-ral visszateszi. Ja, persze két helyre a két töredéket, előtte shiftelések is vannak.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

>,Marmint oke hogy a C standard megengedo, de akkor ugye elegge oda a hordozhatosag.

A micsoda van oda? A bitfieldek soha a fájó életben nem voltak hordozhatóak. Ja meg semmi más sem, ami nem a méretével osztható címen van (padding), vagy ami egynél több bájtból áll (endianness).

Imigy?

#define INTERRUPT_test_and_run(ready,C1,C2,C3,funct) \
    do                          \
     {  if ( (C1)&(C2)&(C3) )   \
         {   funct();           \
             ready |= true;     \
         }                      \
     } while(0)

void __interrupt() INTERRUPT_InterruptManager(void) 
{
 bool ready;

 ready = false;
 INTERRUPT_test_and_run(ready, PIE3, PIR3, _PIR3_TMR0IF_MASK, tmr0_isr);
 INTERRUPT_test_and_run(ready, PIE4, PIR4, _PIR4_U1RXIF_MASK, rx_isr);
 INTERRUPT_test_and_run(ready, PIE4, PIR4, _PIR4_U1TXIF_MASK, tx_isr);
 if (!ready) RESET();        // unhandled interrupt
}

Szerinted a (*p).member működne a p->member helyett?

$ cat x.c
#include <stdio.h>

struct obj
 {      int memb;
 } ;

int funct(struct obj *o)
{
 printf("x=%d\n",(*o).memb);
 return(0);
}

int main(void)
{
 struct obj someobject;
 (&someobject)->memb=6*7;
 funct(&someobject);
 return(0);
}

$ gcc -Wall -pedantic -ansi -O3 -o x x.c
$ ./x
x=42
$

Az mas kerdes hogy forditva csinalom es a obj.memb-ektol szabadulok es objptr->memb-eket hasznalok ahol csak lehet (azaz kb mindenhol :]).

Arra utaltam, hogy noha kényelmes, jól olvasható a struktúrára mutató pointer tagja -> jelölés, lényegében redundáns. Bár, amikor a struktúra tag is pointer, ami mutat egy tagra, akkor nem volna túl jó ez:

(*(*p).ptr).member

Lényegesen jobban olvasható a p->ptr->member
 

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Elengedtem a makrót, végül ezt csináltam:

typedef void (*isr_t)(void);

static inline bool interrupt_test_and_run(uint8_t pie, uint8_t pir, uint8_t mask, isr_t isr) {

    bool ret;

    ret = isr && (pie & pir & mask);
    if (ret) isr();
    return ret;
}

void __interrupt() INTERRUPT_InterruptManager(void) {

    bool done;

    done = false;
    done |= interrupt_test_and_run(PIE3, PIR3, _PIR3_TMR0IF_MASK, tmr0_isr);
    done |= interrupt_test_and_run(PIE4, PIR4, _PIR4_U1RXIF_MASK, rx_isr);
    done |= interrupt_test_and_run(PIE4, PIR4, _PIR4_U1TXIF_MASK, tx_isr);
    if (!done) RESET();                                                         // unhandled interrupt
}

Kérdés, hogy a függvénypointer átadásánál rájön-e arra, hogy ez fordítási időben eldől, mert már akkor tudható a cím, és közvetlenül egy hívássá fordítható mindenféle futásidejű címszámítás nélkül.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Igen, néha szoktam beleírni efféle védelmeket. NULL ellen van. :) Már csak azért is, mert sok függvényemet írom meg úgy, hogy ha kérek tőle valamit, akkor átadom a változó címét, ha nem, akkor NULL-t adok meg neki, persze ettől nem borul fel:

int8_t valami(uint16_t *var) {

if (var) *var = kifejezés;
return 0;
}

uint16_t result;

valami(NULL);

vagy

valami(&result);
 

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE