C xc8 rozkald double na byty
Jan Waclawek
konfera na efton.sk
Úterý Květen 16 23:19:42 CEST 2017
>Skusit nieco mozem, ale neviem presne co mam skusit, daj mi prosim
>blizsi popis, stracam sa v tom.
Tak som prisiel na to, ze neviem. Skusim to zhrnut.
Ulohou je s pouzitim prekladaca (podla moznosti nativneho pre danu
platformu, aby sa vylucili medziplatformove nekompatibility binarnej
reprezentacie typov) vygenerovat binarny inicializator pre float (alebo
akukolvek inu viac-nez-jednobytovu premennu) s pouzitim makra
__EEPROM_DATA(), ktore akceptuje jednotlive byty.
0. Navod od Jana Zuffu
http://list.hw.cz/pipermail/hw-list/2017-May/499344.html, ktory v tom XC8
funguje, vklada vyraz typu
(123.4 >> 8) & FF
do makra __EEPROM_DATA(). Kedze ten vyraz je nelegalny v C (operator >>
vyzaduje aby oba operandy boli celociselne), toto funguje len vdaka tomu,
ze __EEPROM_DATA() je makro ktore vedie na inline assembler, v ktorom je
uvedeny vyraz uz legalny (k tomu, aby bolo jasne, co v tom asembleri je
alebo nie je legalne treba nastudovat spomenutu kapitolu 6 z manualu XC8).
Toto teda nie je ani v naznaku prenositelne.
Bolo by super vidiet, ako presne vyzera to makro __EEPROM_DATA() (ak je to
vobec makro a nie intrinzicka funkcia prekladaca); a ako ju kompilator
prelozi do asembleru.
Pre dalsie pokusy, ktore som dufal, ze budu prenositelne, som si pre
avr-gcc vyrobil moje makro __EEPROM_DATA() ktore vlozene byty pouzije ako
inicializator pola bytov.
1. Type punning skrz pretypovanie smernikov, podla panov kolegov Hamouza
http://list.hw.cz/pipermail/hw-list/2017-May/499341.html a Stengla
http://list.hw.cz/pipermail/hw-list/2017-May/499449.html - vid makro BB3()
a "attempt 2" dole.
Toto si vyzaduje definovat inicializovanu float premennu ff; napriek tomu
ze je oznacena ako const, tie jednotlive "rozobrate byty" (ktorych hodnotu
by teda mohol poznat v case kompilacie) v inicializatore prekladac
nepoklada za konstantne a tym padom nedovoli pouzit ako inicializator
globalnej ci statickej premennej. Na ukazku je to teda pouzite ako
inicializovana lokalna premenna, ktoru prekladac samozrejme drzi na stacku
a nema pre nu inicializator, ale pri vstupe do funkcie ho nakopiruje z
povodnej premennej ff:
61 003c 8091 0000 lds r24,ff
62 0040 9091 0000 lds r25,ff+1
63 0044 2091 0000 lds r18,ff+2
64 0048 3091 0000 lds r19,ff+3
65 004c 8987 std Y+9,r24
66 004e 9A87 std Y+10,r25
67 0050 2B87 std Y+11,r18
68 0052 3C87 std Y+12,r19
Tadialto cesta teda nevedie.
2a. Type punning skrze union, podla pana kolegu Mraza
http://list.hw.cz/pipermail/hw-list/2017-May/499350.html - makro BB2() v
"attempt 1".
Vysledok je - nie prilis prekvapujuco - presne ten isty.
53 0024 8091 0000 lds r24,rb
54 0028 9091 0000 lds r25,rb+1
55 002c 2091 0000 lds r18,rb+2
56 0030 3091 0000 lds r19,rb+3
57 0034 8D83 std Y+5,r24
58 0036 9E83 std Y+6,r25
59 0038 2F83 std Y+7,r18
60 003a 3887 std Y+8,r19
2b. Type punning skrze union s pouzitim compound literal podla C99 6.5.2.5
(makro BB1()) - ten XC8 je len ciastocne C99-kompatibilny (je proklamovany
ako C89, a compound literals su novinka v C99), takze ani toto vlastne nie
je riesenie povodneho problemu, ale vkladal som do neho velke nadeje, lebo
je tu vyluceny medzikrok cez pomocnu premennu.
A nic... gcc ani toto nepoklada za konstantny inicializator, takze znova
len na ukazku ako lokalna premenna, aj ked ju gcc - celkom pochopitelne -
inicializuje konstantami (ale nie z inicializacneho segmentu):
45 0014 8DEC ldi r24,lo8(-51)
46 0016 8983 std Y+1,r24
47 0018 8CEC ldi r24,lo8(-52)
48 001a 8A83 std Y+2,r24
49 001c 86EF ldi r24,lo8(-10)
50 001e 8B83 std Y+3,r24
51 0020 82E4 ldi r24,lo8(66)
52 0022 8C83 std Y+4,r24
Este si dovolim poznamky ku generovaniu inicializatora inak nez pouzitim
__EEPROM_DATA() (t.j. tiez nie odpoved na povodnu otazku):
3. Dovolil som si inline asm v tom avr-gcc. Toto funguje a je pochopitelne
dokonale neprenositelne, mozno ani medzi roznymi inkarnaciami (targetmi)
gcc, ba dokonca ani medzi roznymi verziami avr-gcc (skusil som 4.2 a 4.8 a
v oboch to fungovalo, ale ten inline asm pochopitelne nie je
standardizovany takze to v novsej verzii moze zacat nefungovat). Kedze,
ako som uz bol pisal, avr-as "nezozerie" floating-point konstanty, tuna
som si vypomohol podvodom, ked kompilator pri preklade z inline asembleru
pouzije constraint "i" (co je predpis ze "vyrob z daneho parametra integer
konstantu"), a asembler ho nasledne rozbije na byty. Ako parameter
namiesto (FF) ani tu nemozem pouzit (FF >> 8) lebo to je C vyraz
vyhodnocovany kompilatorom, t.j. pre neho plati ta podmienka ze operandy
>> musia byt celociselne.
Takto to prelozi kompilator:
18 .section .eeprom
19 0000 CD .byte 0x42f6cccd & 0xFF
20 0001 CC .byte 0x42f6cccd >> 8 & 0xFF
21 0002 F6 .byte 0x42f6cccd >> 16 & 0xFF
22 0003 42 .byte 0x42f6cccd >> 24 & 0xFF
23 .text
a s tymto si uz asembler hravo poradi.
4. Jeden pomerne dobry sposob, nie prenositelny v detailoch ale ako
myslienka mozno uplatnitelny u mnohych prekladacov, je vygenerovanie
inicializatora pre nativne inicializovane premenne (ci uz RAM alebo FLASH)
a potom ho nejakym sposobom vyextrahovat z vysledneho binaru. Ta extrakcia
je pochopitelne zavisla od toolchainu, ale castokrat su tie inicializatory
pomerne dobre oddelitelne vdaka umiestneniu v osobitnych sekciach. Toto
moze nezafungovat u prechytralych prekladacov (ktorym je aj ten XC8
mimochodom, a do urcitej miery aj SDCC), ktore bud inicializuju kodom a
nie kopirovanim bloku, alebo ktore maju tendenciu brutalne vyoptimalizovat
nepouzivane alebo malo pouzivane premenne az do ich uplnej eliminacie
spomedzi inicializovanych premennych.
wek
#include <stdint.h>
#define SUPERGLUE(a, b) a##b
#define GLUE(a, b) SUPERGLUE(a, b)
#define __EEPROM_DATA(a, b, c, d) const volatile uint8_t GLUE(aa,
__LINE__)[] = {a, b, c, d};
#define FF 123.4f // 0x42f6cccd
// ---------- attempt 1 - type punning through union
// http://list.hw.cz/pipermail/hw-list/2017-May/499350.html
#define REAL float
#define BYTE uint8_t
typedef union rb_u {
const REAL r;
const BYTE b[sizeof (REAL)];
} rbu_t;
const rbu_t rb = {.r=FF};
#define BB1(f, byte) ((const rbu_t){.r = (f)}).b[(byte)]
#define BB2(f, byte) rb.b[byte]
// ---------- attempt 2 - type punning through pointer cast
// http://list.hw.cz/pipermail/hw-list/2017-May/499341.html
// http://list.hw.cz/pipermail/hw-list/2017-May/499449.html
#define BB3(f, byte) (((uint8_t *)&(f))[(byte)])
const REAL ff = FF;
// ---------- attempt 3 - gcc-specific inline asm
void dummy(void) __attribute__((naked));
void dummy(void) {
__asm(
"\n\t" ".section .eeprom"
"\n\t" ".byte %0 & 0xFF"
"\n\t" ".byte %0 >> 8 & 0xFF"
"\n\t" ".byte %0 >> 16 & 0xFF"
"\n\t" ".byte %0 >> 24 & 0xFF"
"\n\t" ".text"
:
: "i" (FF)
);
};
int main(void) {
__EEPROM_DATA(BB1(FF, 0), BB1(FF, 1), BB1(FF, 2), BB1(FF, 3));
__EEPROM_DATA(BB2(FF, 0), BB2(FF, 1), BB2(FF, 2), BB2(FF, 3));
__EEPROM_DATA(BB3(ff, 0), BB3(ff, 1), BB3(ff, 2), BB3(ff, 3));
}
Další informace o konferenci Hw-list