Macht es einen Unterschied für den (inline) Assembler, ob Operanden
- als input-output (constraint modifier "+"), oder
- als input-output UND input
gelistet sind?
Kontext:
Ich nutze folgende strncmp_P-ähnliche Funktion in einem C Programm.
Diese hat für mich den Vorteil, dass
- der Parameter explizit den __flash Adressraum verwendet (statt
strncmp_P generic AS, vermeidet Warnung)
- die Funktion relativ effizient inlined wird (kein call overhead,
keine parameter register)
- nur einen uint8_t als Parameter akzeptiert (statt size_t), und
- nur einen int8_t zurueck gibt (statt int)
Die erste Variante hat die Parameter sowohl in der output operanden
Liste (input-output) als auch in der input(-only) operanden Liste
aufgeführt.
Beide Varianten werden in einer längeren Funktion verwendet und korrekt
geinlined. Allerdings benötigt die erste Variante weniger Register und
ist damit kürzer (push, pop) als die zweite Variante.
Wieso?
Es hängt offensichtlich mit der zusätzlichen Auflistung als input
Operanden zusammen.
Ist das falsch von mir bzw. sage ich damit (fälschlicherweise), dass die
Register unverändert bleiben (dafür hätte ich ja input-output constraint
modifier "+" verwendet)?
Oder ist es nur Zufall, weil der inline-Asm andere (hier ungünstigere)
Register auswählt?
Danke,
???
3? schrieb:> Beide Varianten werden in einer längeren Funktion verwendet und korrekt> geinlined. Allerdings benötigt die erste Variante weniger Register und> ist damit kürzer (push, pop) als die zweite Variante.
Damit meine ich natürlich, dass strncmp_P_ Funktion nur einmal verwendet
wird (in einer anderen, längeren Funktion). Aber wenn ich mit der erste
Variante kompiliere, ist das Programm kürzer, als wenn ich die zweite
Variante kompiliere.
3? schrieb:> Ist das falsch von mir bzw. sage ich damit (fälschlicherweise), dass die> Register unverändert bleiben (dafür hätte ich ja input-output constraint> modifier "+" verwendet)?
Ich glaube, ich habe in einem anderen Fall genau das gleiche Problem.
Ein im asm statement verändertes Register wird nicht neu geladen, obwohl
eine schreibende Abhängigkeit angegeben ist:
1
staticvoidfun(volatileuint8_t*samples)
2
{
3
uint8_tn;
4
asmvolatile(
5
"ld %[n], %a[ptr]+ \n"
6
:[ptr]"=z"(samples),[n]"=&r"(n)
7
:"0"(samples)
8
:/* no clobbers */);
9
samples[0]=n;
10
}
Das ergibt:
1
ld r24, Z+
2
st Z, r24
Der gleiche Code wird mit "+" output constraint generiert:
1
staticvoidfun(volatileuint8_t*samples)
2
{
3
uint8_tn;
4
asmvolatile(
5
"ld %[n], %a[ptr]+ \n"
6
:[ptr]"+z"(samples),[n]"=&r"(n)
7
:/* no input operands */
8
:/* no clobers */);
9
samples[0]=n;
10
}
Korrekt wäre z.B.
1
movw r22, r30 ; backup Z
2
ld r24, Z+
3
movw r30, r22 ; restrore Z
4
st Z, r24
AVR-GCC aus den Debian repositores (gcc-avr 5.4.0+Atmel3.6.1-2), sollte
gleich sein zu AVR-Studio:
TO schrieb:> Ich glaube, ich habe in einem anderen Fall genau das gleiche Problem.> Ein im asm statement verändertes Register wird nicht neu geladen, obwohl> eine schreibende Abhängigkeit angegeben ist:>>
1
>staticvoidfun(volatileuint8_t*samples)
2
>{
3
>uint8_tn;
4
>asmvolatile(
5
>"ld %[n], %a[ptr]+ \n"
6
>:[ptr]"=z"(samples),[n]"=&r"(n)
7
>:"0"(samples)
8
>:/* no clobbers */);
9
>samples[0]=n;
10
>}
> Das ergibt:
1
> ld r24, Z+
2
> st Z, r24
Soweit korrekt.
> Der gleiche Code wird mit "+" output constraint generiert:
1
>staticvoidfun(volatileuint8_t*samples)
2
>{
3
>uint8_tn;
4
>asmvolatile(
5
>"ld %[n], %a[ptr]+ \n"
6
>:[ptr]"+z"(samples),[n]"=&r"(n)
7
>:/* no input operands */
8
>:/* no clobers */);
9
>samples[0]=n;
10
>}
Das ist gleichbedeutend mit dem obigen Code: samples ist Input und
Output, und beide sind im gleichen Register "z".
> Korrekt wäre z.B.
1
> movw r22, r30 ; backup Z
2
> ld r24, Z+
3
> movw r30, r22 ; restrore Z
4
> st Z, r24
Nein, das wäre falsch.
samples ist Ouput-Operand aus "z", d.h. im weiteren muss der Wert aus Z
für samples genommen werden, also der durch Post-Increment erhöhte.
> Ich fände es großartig, hier eine Lösung zu haben...
Also eine bessere Verständnis der Semantik von Inline-Asm.
Wenn das ursprüngliche samples weiter verwendet werden soll, dann zum
Beispiel so:
1
uint8_tn;
2
volatileuint8_t*tmp;
3
asmvolatile(
4
"ld %[n], %a[ptr]+ \n"
5
:[ptr]"=z"(tmp),[n]"=r"(n)
6
:"0"(samples));
7
samples[0]=n;
oder so:
1
uint8_tn;
2
volatileuint8_t*tmp=samples;
3
asmvolatile(
4
"ld %[n], %a[ptr]+ \n"
5
:[ptr]"+z"(tmp),[n]"=r"(n));
6
samples[0]=n;
Es wird dann das veränderte Z nach tmp geschrieben, nicht zurück nach
samples.
Außerdem braucht's hier für n kein early-clobber.
TO schrieb:> In der avr-libc wird teilweise genau das gleiche gemacht, aber es> funktioniert. avr/pgmspace.h:
Da muss gekennzeichnet werden, dass Z (also __addr16) durch das asm
verändert wird. Es wird in der Folge aber nicht mehr verwendet.
Auch in dem Beispiel hätte man
1
"=z"(__addr16):"1"(__addr16)
kürzer schreiben können als
1
"+z"(__addr16):
Obgleich __addr16 nicht weiter verwendet wird, muss Z bei den Outputs
auftauchen. Ansonsten würde man dem Compiler sagen, dass Z nicht
verändert wird, was nicht stimmt.
>> Nein, das wäre falsch.>> samples ist Ouput-Operand aus "z", d.h. im weiteren muss der Wert aus Z> für samples genommen werden, also der durch Post-Increment erhöhte.
Aha! Stimmt, die output Operanden sind lvalues.
Herzlichen Dank!
Beim Zugriff mit obigem Makro wird der Offset also doppelt addiert.
Davon ab ist progmem eh überflüssig, weil .rodata im Flash liegt und das
Linker-Skript 0x4000 addiert:
Johann L. schrieb:> TO schrieb:>> In der avr-libc wird teilweise genau das gleiche gemacht, aber es>> funktioniert. avr/pgmspace.h:> [c]>> #define __LPM_word_tiny__(addr) \
...
> Dieses Makro ist nicht nur überflüssig, sondern auch falsch!
Ich habe es mir nicht im Detail angeschaut, sondern nur ein ähnliches
Beispiel zu meinem Problem gesucht. Das Makro ist in der Form aber in
der avr-libc mit Microchips patches enthalten:
1
$ cd /run/shm
2
$ apt source avr-libc
3
Reading package lists... Done
4
Selected version '1:1.8.0+Atmel3.5.0-1' (stretch) for avr-libc