Forum: Mikrocontroller und Digitale Elektronik avr-gcc-9.3.0 inline assembler und INT8_MIN


von sep (Gast)


Lesenswert?

Hallo,

ich habe einmal eine komische Frage...
Ich habe hier ein Minimaleispiel:
1
#include <stdint.h>
2
#include <limits.h>
3
#include <stdlib.h>
4
5
int __attribute__(( OS_main )) main( void ) {
6
  int8_t res;
7
  __asm__ volatile( "ldi %0, %1" : "+d" ( res ) : "M" ( INT8_MIN ) : );
8
  return EXIT_SUCCESS;
9
}

Dieser compiliert leider nicht. Ich bekomme dort folgenden Fehler:
1
$ avr-gcc -Os -std=gnu99 -mmcu=atmega328p -o main.o main.c
2
main.c: In Funktion »main«:
3
main.c:8:3: Warnung: asm-Operand 1 passt wahrscheinlich nicht zu den Bedingungen
4
    8 |   __asm__ volatile( "ldi %0, %1" : "+d" ( res ) : "M" ( INT8_MIN ) : );
5
      |   ^~~~~~~
6
main.c:8:3: Fehler: unmögliche Bedingung in »asm«

Mache ich aus dem INT8_MIN ein 0x80 kompiliert das.
Hat jemand eine Erklärung dazu? Oder etwas wie man das mit dem define 
INT8_MIN zum laufen kriegen kann?

Ich habe einmal geschaut. Und INT8_MIN ist bei mir als ( ( -INT8_MAX ) - 
1 ) definiert. Ersetze ich INT8_MIN durch INT8_MAX, kompiliert das noch. 
Sobald ich ein -INT8_MAX draus mache... habe ich den selben Fehler wie 
bei INT8_MIN... komisch.

Ich wäre für jede Hilfe dankbar und würde gerne wissen, warum das mit 
INT8_MAX, 0x80 funktioniert, aber nicht mit INT8_MIN

Vielen Dank schon einmal für die Hilfe
sep

von Rolf M. (rmagnus)


Lesenswert?

Laut Dokumentation muss der Wert im Bereich 0 bis 255 sein. Du willst 
aber einen Wert von -128 übergeben. Anscheinend kann man das aber nicht 
mehr nachträglich nach uint8_t wandeln. Ich vermute, das hat mit der 
Integer Promotion zu tun.
Mir fällt dafür jetzt so keine Lösung ein.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

"M" ist nicht die richtige Constraint, nimm "n". Und Operand 0 ist 
output only also "+d".

von Rolf M. (rmagnus)


Lesenswert?

Johann L. schrieb:
> "M" ist nicht die richtige Constraint, nimm "n".

Damit bekommt man zwar den Wert rein, aber der Assembler akzeptiert ihn 
nicht, da er einen positiven 8-Bit-Wert will. Deshalb soll man bei ldi 
laut avr-libc-Doku ja auch "M" verwenden.
Was man aber machen kann ist "n" und dann "ldi %0, lo8(%1)".

von sep (Gast)


Lesenswert?

Das war auch mein Ansatz, das ich ihn anschließend in ein uint8_t caste. 
Auch leider ohne Erfolg..
Ich nehme immer die Seite zu Rate: 
https://rn-wissen.de/wiki/index.php/Inline-Assembler_in_avr-gcc

Hm. Da ist halt ldi mit M. Da steht zwar was von 0..255 aber ich dachte 
nun nicht dass ein - ein so großen Unterschied macht. Vor allem da 
int8_t ja die gleiche Breite hat wie uint8_t und der Rest 
Interpretationsasche ist... ...

Vielen Dank auf alle Fälle dafür. ;)
sep

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

sep schrieb:
> Das war auch mein Ansatz, das ich ihn anschließend in ein uint8_t caste.
> Auch leider ohne Erfolg..
> Ich nehme immer die Seite zu Rate:
> https://rn-wissen.de/wiki/index.php/Inline-Assembler_in_avr-gcc
>
> Hm. Da ist halt ldi mit M. Da steht zwar was von 0..255 aber ich dachte
> nun nicht dass ein - ein so großen Unterschied macht. Vor allem da
> int8_t ja die gleiche Breite hat wie uint8_t und der Rest
> Interpretationsasche ist...

Bei Constraints geht es um Werte, nicht um Bitbreiten, hier zum Beispiel 
die Definition von Constraint "M":

http://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/config/avr/constraints.md;h=d7a8ae67afaee5c1acc40a0a4f685d0872741f6d#l75
1
(define_constraint "M"
2
  "Integer constant in the range 0 @dots{} 0xff."
3
  (and (match_code "const_int")
4
       (match_test "ival >= 0 && ival <= 0xff")))

"Mein" avr-as akzeptiert Werte von -255 bis 255 im LDI.  Eine passende 
Constraint gibt's nicht dafür, aber man kann sich eine zusammenstöpseln 
aus "M" und "Cn8":
1
    __asm ("ldi R16, %0" :: "nMCn8" (-255));
1
  ldi R16, -255

Allerdings ist der AVR-Instruktionssatz so gestrickt, dass es nicht 
sinnvoll ist, Constaints zu verwenden, die echte Teilmengen von "n" 
sind, also von "zur Compilezeit bekannter Integer".  Zweckmäßig wäre das 
z.B. wenn es eine ADD instruktion gäbe, die zwei Register addieren kann 
oder auch Register und Immediate im Bereich 0...255.  Das könnte man 
etwa folgenden Code schreiben:
1
char add (char akku, char val)
2
{
3
    __asm ("ADD %0, %1" : "+r" (akku) : "rM" (val)); // #1
4
    val = -1;
5
    __asm ("ADD %0, %1" : "+r" (akku) : "rM" (val)); // #2
6
    val = 100;
7
    __asm ("ADD %0, %1" : "+r" (akku) : "rM" (val)); // #3
8
    __asm ("ADD %0, %1" : "+r" (akku) : "rM" (-42)); // #4
9
    return akku;
10
}
1
add:
2
  ADD r24, r22
3
4
  ldi r25,lo8(-1)
5
  ADD r24, r25
6
7
  ADD r24, 100
8
9
  ldi r18,lo8(-42)
10
  ldi r19,lo8(-1)
11
  ADD r24, r18
12
13
  ret

In Fall #1 ist %1 eine Variable, die in einem Register lebt, und diese 
kann direkt eingesetzt werden.

In Fall #2 wird val = -1 verwendet, und ADD kann diese Konstante nicht 
verdauen, daher muss sie in ein Register (hier R25) geladen werden.

In Fall #3 wird 100 addiert, und unsere ADD-Instruktion kann diese 
Konstante addieren, daher darf der Compiler "ADD *,100" erzeugen.

In Fall #4 ist die Konstante wieder außerhalb des erlaubten Bereichs, 
und zwar ein int.  Daher wird -42 in ein 16-Bit Register (hier R19:R18) 
geladen.

So hingeschrieben ist dass alles etwas seltsam, aber wenn man das ADD 
als Makro verfügbar macht, sieht's plausibler aus:
1
#define ADD(a, b)  __asm ("ADD %0, %1" : "+r" (a) : "rM" ((int8_t) (b)))
2
3
int8_t add (int8_t akku, int8_t val)
4
{
5
    ADD (akku, val);
6
    ADD (akku, -1);
7
    ADD (akku, 100);
8
    ADD (akku, -42);
9
    return akku;
10
}
Unter der Annahme ADD sei eine 8-Bit Addition gibt es ein Cast auf 
int8_t, daher lädt Fall #4 nur noch ein 8-Bit Register mit -42.

Der AVR-Instruktionssatz sieht aber wie gesagt anders aus, da sieht man 
schon am Mnemonic, welche Typen die Operanden haben, z.B. ADD vs. SUBI.

Für AVR würde ein entsprechendes ADD-Makro also deutlich kompliziertes 
aussehen:
1
#define ADD(a, b)                                                       \
2
    do {                                                                \
3
        if (__builtin_constant_p (b) && b == 0)                         \
4
            (void) 0;                                                   \
5
        else if (__builtin_constant_p (b) && b + 255 <= 510)            \
6
            __asm ("SUBI %0, %n1" : "+r" (a) : "MCn8" (b));             \
7
        else                                                            \
8
            __asm ("ADD %0, %1" : "+r" (a) : "r" (b));                  \
9
    } while (0)

Die Vereinigung von "M" und "Cn8" zu nutzen bring hier aber kein Vortail 
gegenüber "n".

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.