Forum: Compiler & IDEs GCC generiert 2 Jumptables bei Switch


von Dennis B. (higret)


Lesenswert?

Hi

ich hab schon im Forum gesucht, aber nicht das richtig gefunden.

Ich hab in der main() eine Schleife in der eine Switch-Anweisung steht.

Der avr-gcc (GCC) 4.1.3 20070724 generiert daraus 2 Jumptables.

Das ganze wird für nen Atmega8 kompiliert

Hier 3 Ausschnitte
1
uint16_t t = ...;
2
switch (t) {
3
  case 0x01: set_act_date();
4
    break;
5
  case 0x02: get_act_date();
6
    break;
7
  case 0x03: get_alarm_info();
8
    break;
9
  case 0x04: set_alarm_info();
10
    break;
11
  case 0x05: write_alarm_with_index();
12
    break;
13
  case 0x06: get_alarm_with_index();
14
    break;
15
  case 0x07: add_alarm();
16
    break;
17
  case 0x08: delete_alarm_with_index();
18
    break;
19
  case 0x09: get_clock_config();
20
    break;
21
        case ...
22
23
}

in r31:r30 ist t abgelegt

mit dem ijmp springt er in den zweiten ASM-Ausschnitt zu Zeile 26+Index

und von da aus dann wieder zurück zur Zeile 65c
1
 64e:  31 97         sbiw  r30, 0x01  ; 1
2
 650:  e9 30         cpi  r30, 0x09  ; 9
3
 652:  f1 05         cpc  r31, r1
4
 654:  88 f7         brcc  .-30       ; 0x638 <main+0x8>
5
 656:  ed 5e         subi  r30, 0xED  ; 237
6
 658:  ff 4f         sbci  r31, 0xFF  ; 255
7
 65a:  09 94         ijmp
8
 65c:  5b df         rcall  .-330      ; 0x514 <set_act_date>
9
 65e:  ec cf         rjmp  .-40       ; 0x638 <main+0x8>
10
 660:  ad df         rcall  .-166      ; 0x5bc <get_act_date>
11
 662:  ea cf         rjmp  .-44       ; 0x638 <main+0x8>
12
 664:  7b df         rcall  .-266      ; 0x55c <get_alarm_info>
13
 666:  e8 cf         rjmp  .-48       ; 0x638 <main+0x8>
14
 668:  20 de         rcall  .-960      ; 0x2aa <set_alarm_info>
15
 66a:  e6 cf         rjmp  .-52       ; 0x638 <main+0x8>
16
 66c:  79 de         rcall  .-782      ; 0x360 <write_alarm_with_index>
17
 66e:  e4 cf         rjmp  .-56       ; 0x638 <main+0x8>
18
 670:  16 de         rcall  .-980      ; 0x29e <get_alarm_with_index>
19
 672:  e2 cf         rjmp  .-60       ; 0x638 <main+0x8>
20
 674:  33 de         rcall  .-922      ; 0x2dc <add_alarm>
21
 676:  e0 cf         rjmp  .-64       ; 0x638 <main+0x8>
22
 678:  2b df         rcall  .-426      ; 0x4d0 <get_clock_config>
23
 67a:  de cf         rjmp  .-68       ; 0x638 <main+0x8>

die rjmp zu main+0x8 gehen zur Anfang der Hauptschleife
1
   0:  1c c0         rjmp  .+56       ; 0x3a <__ctors_end>
2
   2:  2b c1         rjmp  .+598      ; 0x25a <__vector_1>
3
   4:  34 c0         rjmp  .+104      ; 0x6e <__bad_interrupt>
4
   6:  33 c0         rjmp  .+102      ; 0x6e <__bad_interrupt>
5
   8:  32 c0         rjmp  .+100      ; 0x6e <__bad_interrupt>
6
   a:  31 c0         rjmp  .+98       ; 0x6e <__bad_interrupt>
7
   c:  30 c0         rjmp  .+96       ; 0x6e <__bad_interrupt>
8
   e:  2f c0         rjmp  .+94       ; 0x6e <__bad_interrupt>
9
  10:  2e c0         rjmp  .+92       ; 0x6e <__bad_interrupt>
10
  12:  2d c0         rjmp  .+90       ; 0x6e <__bad_interrupt>
11
  14:  2c c0         rjmp  .+88       ; 0x6e <__bad_interrupt>
12
  16:  32 c3         rjmp  .+1636     ; 0x67c <__vector_11>
13
  18:  2a c0         rjmp  .+84       ; 0x6e <__bad_interrupt>
14
  1a:  29 c0         rjmp  .+82       ; 0x6e <__bad_interrupt>
15
  1c:  28 c0         rjmp  .+80       ; 0x6e <__bad_interrupt>
16
  1e:  27 c0         rjmp  .+78       ; 0x6e <__bad_interrupt>
17
  20:  26 c0         rjmp  .+76       ; 0x6e <__bad_interrupt>
18
  22:  25 c0         rjmp  .+74       ; 0x6e <__bad_interrupt>
19
  24:  24 c0         rjmp  .+72       ; 0x6e <__bad_interrupt>
20
  26:  1a c3         rjmp  .+1588     ; 0x65c <main+0x2c>
21
  28:  1b c3         rjmp  .+1590     ; 0x660 <main+0x30>
22
  2a:  1c c3         rjmp  .+1592     ; 0x664 <main+0x34>
23
  2c:  1d c3         rjmp  .+1594     ; 0x668 <main+0x38>
24
  2e:  1e c3         rjmp  .+1596     ; 0x66c <main+0x3c>
25
  30:  1f c3         rjmp  .+1598     ; 0x670 <main+0x40>
26
  32:  20 c3         rjmp  .+1600     ; 0x674 <main+0x44>
27
  34:  01 c3         rjmp  .+1538     ; 0x638 <main+0x8>
28
  36:  20 c3         rjmp  .+1600     ; 0x678 <main+0x48>

Wie kann ich den GCC dazu bringen, dass er direkt zur Zeile 65c springt 
und nicht erst den Umweg über den erweiterten Interrupt Vektor

lg

von Andreas K. (a-k)


Lesenswert?

Benimmt er sich falsch oder vermutest du nur anhand des etwas 
irreführenden Listings dass er es täte?

Denn bei
  676:  e0 cf         rjmp  .-64       ; 0x638 <main+0x8>
springt er natürlich nicht nach main+0x08, sondern nach 0x638 
(0x676+2-64).

Warum im Kommentar main+0x08 drinsteht weiss ich nicht. Aber nicht der 
Kommentar wird ausgeführt, sondern der Code.

von Dennis B. (higret)


Lesenswert?

ich weiß, aber 676+2-64 ist main+0x8 :)

daher der kommentar

main fängt bei 630 an


das ergebnis ist so wie gewollt, aber ich möchte einfach nur das, der µC 
nicht erst 2x springen muss bevor er dann die richtige funktion aufruft

von Andreas K. (a-k)


Lesenswert?

Dennis B. wrote:

> ich weiß, aber 676+2-64 ist main+0x8 :)

Mag sein. Aber drunter schreibst du was von der Interrupt-Tabelle. Und 
die hat ja nun rein garnichts mit der Sache zu tun. Denn wie du selber 
schreibst, geht der Sprung nach main+0x8=0x638 und nicht zur 
Interrupt-Tabelle.

Und da der wichtigste Teil des Codes fehlt, sowohl in Quelle wie im 
Listing, kann man auf die Frage schlecht eine Antwort gegen.

von Dennis B. (higret)


Lesenswert?

hmm dachte das ist verständlich

weil mehr Quellcode posten bringt nix, weil der unwichtig ist.

Denn im ersten ASM Ausschnitt steht ein IJMP, und im Z Register steht

26 + (t-1) (aus der switch anweisung)

und zeile 26 steht im 2 ASM Code.

PS: aber wenn du möchtest, hänge ich gerne ca 1400 Zeilen Code an (400 
in C und 1000 in ASM)

von Andreas K. (a-k)


Lesenswert?

Ok, sorry, ich hatte das verkehrt gelesen.

Der Code hat nur eine einzige Sprungtabelle, nämlich die an 0x26. Das 
Zeug ab 0x65C ist schlicht der Code der einzelnen Komponenten vom 
Switch-Statement. Dass die alle ähnlich aussehen liegt an deinem ebenso 
ähnlichen Quellcode, nicht am Compiler.

Wenn du erreichen willst, dass in der Tabelle die Funktion direkt 
aufgerufen werden, dann solltest du hier kein switch() verwenden, 
sondern eine Funktionstabelle:
1
  typedef void (*funcptr)(void);
2
  void funcptr[9] = { set_act_date, get_act_date, ... };
3
4
  if (t >= 1 && t <= 9) {
5
     functab[t-1];
6
  }

von Dennis B. (higret)


Lesenswert?

ah ok

danke


hab das jetzt mit nem Funktionspointer-Array gemacht. Der Code ist um ca 
80 Words kleiner.

Danke

von Oliver (Gast)


Lesenswert?

>Wie kann ich den GCC dazu bringen, dass er direkt zur Zeile 65c springt
>und nicht erst den Umweg über den erweiterten Interrupt Vektor

Aus akademischem Interesse ja ganz interessant, aber gibt es bei deiner 
Anwendung auch einen praktischen Grund?

Oliver

von Klaus (Gast)


Lesenswert?

> Der Code ist um ca 80 Words kleiner.

Ich denke mal, das war der praktische Grund...

von Dennis B. (higret)


Lesenswert?

Ja da liegt Klaus richtig, der Code sollte kleiner werden und schneller 
ist der Code wohl auch.

von yalu (Gast)


Lesenswert?

Der GCC hat für berechnete Switch-Anweisungen ein festes Code-Muster,
das einen IJMP in eine Serie von RJMPs vorsieht. Das ist für den Fall,
das in den einzelnen Cases unterschiedlich lange Anweisungen stehen,
auch optimal. In deinem Fall steht aber in jedem Case nur ein
einzelner RCALL, der natürlich mit dem RJMP aus der Tabelle zu einem
einzelnen Befehl vereint werden könnte.

Das wäre eine zusätzliche Optimierungregel, die aber offensichtlich
nicht implementiert ist. Vielleicht haben die Entwickler auch deswegen
darauf verzichtet, weil solche Konstrukte sowieso meist mit einem
Array aus Funktionspointern realisiert werden, wie du es jetzt ja
schließlich ebenfalls getan hast.

Die Funktionspointerlösung hat den den Vorteil, dass der Sourcecode
kürzer wird, und die Ausführungszeit ist auch dann unabhängig von der
Anzahl der unterschiedlichen Werte von t, wenn der Compiler bei
Switches keine Optimierung durch berechnete Sprünge kann.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.