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