<i>Zbylé dvě možnosti FCMP/LT či FCMP/GE je však nutné v případě potřeby implementovat pomocí většího množství instrukcí.</i>
Řekl bych, že bude stačit pouze vyměnit pořadí operandů a pak kombinace T == 0 a T != 0.
Ten výčet denormalizovaných nul je dost matoucí, chvilku mi trvalo, než jsem se zorientoval, proč je to jednou klesající (kladné nuly) a jednou stoupající (záporné nuly, či klesající, když je berem jako záporné).
Ale jinak díky za shrnutí a porovnání s ostatníma architekturama :-)
Ve skutečnosti mě pak napadlo, že to úplně pravda není. Pro NaN a cokoliv či dvě nekonečna je výsledek jakékoli porovnávací operace false (s flag), takže jednoduchá negace by dala true, což by nebylo korektní. V low-level kódu se to dá samozřejmě ignorovat či ověřit errors na konci, ale C specifikace je v tomto IMHO dost strikní, takže v důsledku by po každém porovnání v druhém směru musela následovat kontrola na errors...
Zvláštní je, že i některé další architektury si s tím vystačí, z textu například RISC-V, ale i "konzervativní" SSE v prvních verzích.
To je pravda, ale explicitní testy na NaN nejsou IMHO (pokud mě něco neuniklo) prakticky nikdy zapotřebí, když se nastaví příslušný bit ve Flag a Cause. Podle mě bude stačit, že je zaručeno, jak operace s NaN dopadnou (včetně porovnání, tam na pořadí nezáleží, protože vlastně NaN != NaN).
Je fakt, že jediný příznakový bit nemůže vracet "unordered", jak to umí 80x87 (ta má hned tři flagy).
To jo, NaN je jenom 11...1 exponent a nejednickova mantisa, pokud si dobre pamatuju.
Ale v tom prikladu slo o neco jineho. V pripade, ze napriklad x jen NaN, tak:
if (x < y)
neni stejne jako:
if (!(y >= x))
protoze porovnani vzdycky bude false a druha verze podminky tak bude splnena. V ramci korektniho kodu by tedy kompilator mel obratit operandy, porovnat, otestovat T == 0 a navic zkontrolovat flag porovnani s NaN ci dvema nekonecny.
Jo to chapu, ale napriklad ANSI C to asi nebude presne definovat (muzu odzkouset na procesorech, co to handluji jinak nez 80x87, kde jsou tusim tri nebo dokonce ctyri priznakove bity). No stoji to urcite za test, prekladac by asi nemel tu prvni podminku jen tak transformovat na tu druhou (myslim na urovni generovaneho strojaku).
Mozna C99 uz to ma striktnejsi, napriklad tady popisuji zpresneni semantiky nekterych funkci http://www.drdobbs.com/cpp/c99-and-numeric-computing/184404993
Vysledek:
objdump -d -M intel -S test.o
20: dd d8 fstp st(0) 22: 0f 97 c0 seta al 25: 0f b6 c0 movzx eax,al 28: 89 45 f0 mov DWORD PTR [rbp-0x10],eax int j = !(y<=x); 2b: d9 45 e8 fld DWORD PTR [rbp-0x18] 2e: d9 45 ec fld DWORD PTR [rbp-0x14] 31: d9 c9 fxch st(1) 33: df e9 fucomip st,st(1) 35: dd d8 fstp st(0) 37: 0f 93 c0 setae al 3a: 83 f0 01 xor eax,0x1 3d: 0f b6 c0 movzx eax,al 40: 89 45 f4 mov DWORD PTR [rbp-0xc],eax int k = x<=y; 43: d9 45 ec fld DWORD PTR [rbp-0x14] 46: d9 45 e8 fld DWORD PTR [rbp-0x18] 49: d9 c9 fxch st(1) 4b: df e9 fucomip st,st(1) 4d: dd d8 fstp st(0) 4f: 0f 93 c0 setae al 52: 0f b6 c0 movzx eax,al 55: 89 45 f8 mov DWORD PTR [rbp-0x8],eax int l = !(y>x); 58: d9 45 ec fld DWORD PTR [rbp-0x14] 5b: d9 45 e8 fld DWORD PTR [rbp-0x18] 5e: d9 c9 fxch st(1) 60: df e9 fucomip st,st(1) 62: dd d8 fstp st(0) 64: 0f 97 c0 seta al 67: 83 f0 01 xor eax,0x1 6a: 0f b6 c0 movzx eax,al 6d: 89 45 fc mov DWORD PTR [rbp-0x4],eax
Jestli to chapu dobre, tak na to proste kaslou, jen porovnaji, presunou flagy do EFLAGS a SETxx nastavi vysledek.
(jinak ten kod je tak silene neoptimalizovany....)
No ono by to chtělo -O3, aby byl optimalizovaný :-) Ale pak by to zase kompilátor všechno zahodil.
Zkoušel jsem ještě včera na SSE a vypadá to velmi podobně, až na to, že používají seta (při ostrém porovnání) a setae (při neostrém). Docela zvláštní, že je to jinak, bo na x87 by to mělo fungovat taky:
int cmpLt(float x, float y) { return x < y; } int cmpLe(float x, float y) { return x <= y; } int cmpGt(float x, float y) { return x > y; } int cmpGe(float x, float y) { return x >= y; }
Výsledek (s vynecháním balastu):
_cmpLt: ## @cmpLt ucomiss %xmm0, %xmm1 seta %al movzbl %al, %eax popq %rbp retq _cmpLe: ## @cmpLe ucomiss %xmm0, %xmm1 setae %al movzbl %al, %eax popq %rbp retq _cmpGt: ## @cmpGt .cfi_def_cfa_register %rbp ucomiss %xmm1, %xmm0 seta %al movzbl %al, %eax popq %rbp retq _cmpGe: ## @cmpGe ucomiss %xmm1, %xmm0 setae %al movzbl %al, %eax popq %rbp retq
Tedy zpět, přehlídnul jsem ten ! v původním kódu a zmátl mě ten xor ne eax. Takže ten původní celkem věrně kopíruje zdroják.
seta či setae je v pořádku neboť nastaví 1 v případě, že carry flag 0 a v prvním případě i zero flag je 0. Při NaN jsou všechny P=C=Z = 1, tedy základní podmínka C == 0 v takovém případě selže.
Funguje korektně s NaN tedy jak u x87, tak SSE.
Slušelo by se dodat, že FUCOM přidali až v P6, kdy už hrálo x87 labutí píseň :-) Předchozí FCOM ještě měnila stav na FPU a ten se musel ručně přesouvat do AX a ten pak uložit do FLAGS, to už se ta elegance docela ztrácí. Ale holt ve své době daň za separaci (int)CPU a FPU. Akorát se mohli u 64-bit pak soudruzi poučit z těch nesmyslných 8-bit operandů u setCC a rovnou měnit celý registr, aby si ušetřili další movzXY...
Ale zpátky k SH2 - tam je stále otázka, jak to řeší, bohužel se mi nepovedlo najít žádný online compiler, kde by se to dalo vyzkoušet...
jj, tak hlavně že už u 287 nebylo nutné dělat ty šílenosti s FWAIT :-)
SH-2 nakonec asi vyzkouším přes http://mes.osdn.jp/h8/gcc.html (tedy trošku mě děsí, že v jedné větě zmiňují SuperH, AVR i H-8, no snad ví, co říkají :-)
Tak jsem našel SH4 toolchain v Ubuntu :-) Výsledek kompilace (s double místo float pro jednoduchost):
cmpLt: fcmp/gt dr4,dr6 rts movt r0 cmpLe: fcmp/eq dr4,dr6 bt .L4 fcmp/gt dr4,dr6 .L4: rts movt r0 cmpGt: fcmp/gt dr6,dr4 rts movt r0 cmpGe: fcmp/eq dr6,dr4 bt .L9 fcmp/gt dr6,dr4 .L9: rts movt r0
Tedy pro neostré porovnání skutečně používá eq || gt místo ! gt.
PS: V dřívějším článku bylo přehozeno chování BT a BT/S - BT/S má branch delay, zatímco BT nemá.
V tabulce jsem hodnoty u záporných denormalizovaných čísel raději otočil, protože to sice mělo význam (sign bit je na nejvyšší pozici), ale uznávám, že takto to ze zápisu není jasné. Což mě připomíná, že jsem chtěl oprášit jeden text o problémech^W vlastnostech :-) FPU operací, protože jsem v praxi narazil na několik příkladů, kde lidi FPU používali úplně špatně.
Mikrokontroléry od Renesasu spolu s jejich překladači C/C++ jsou dost oblíbené v automobilovém průmyslu. V této souvislosti mě napadlo, zda autor nezvažuje do seriálu přidat také povídání o platformě Aurix TriCore. Je poměrně zajímavá, vznikla v Evropě a málokdo mimo lidi kolem aut vůbec ví, že existuje. Ale vývojové kity lze koupit už i v Conrad Electronics..
TriCore se totiz snad jinde nez v automotive ci vyjimecne industrial nepouziva... je totiz pekelne drahy. Myslim, ze kdyby Infineon zacal produkovat dostatecne vykonne ARM cipy automotive-grade a nacpal k nim vsechny ty CANy/FlexRaye/Liny + dalsi komponenty dle uziti, byla u toho nizsi cena, tak pro TriCore ani pes nestekne.
https://en.wikipedia.org/wiki/Infineon_AURIX
http://www.infineon.com/cms/en/product/microcontroller/32-bit-tricore-tm-microcontroller/channel.html?channel=ff80808112ab681d0112ab6b64b50805
Jinak je TriCore ale docela pekna hracka, pekne se disassembluje ;). Treba na rozdil od PowerPC, kde cloveku nejaky rlwinm umi slusne zamotat hlavu.
Na Tricore se u nás na katedře pracovalo. Využití kryptografické jednotky pro zrychlení protokolu Message Authenticated CAN navrženého ve Volkswagen research. Kód pro Linux a STM je zde
https://github.com/CTU-IIG/macan
Část pro Tricore je psaná psaná proti SDK, které bylo poskytnuté pod nějakou ošklivou licencí a nesmíme ho tedy publikovat. Bylo to pro Infineon, aby mohl předvést užitečnost čipu ve VW.
Jinak Infineon mé,co se týče použití v mé firmě, tvrdý ban. Snažili se, aby jim komunita pomohla integrovat podporu do NewLib, přitom používají a prodávají v SDK GCC, a při dotazu na zdojové kódy vývojář, který chtěl od komunity integrovat změny do NewLibu tak na prohlášení, že by měli k SDK dávat odkaz, balíček zdrojáků GCC, prohlásil, že nedají tečka.
Takže nevím, jestli si zaslouží propagaci článkem. Doporučuji Tricore považovat za mrtvou architekturu.
Hlavní přednost Tricore je, že je jádro implementované v klasickém LockStep režimu. Dvě CPU jádra zpracovávají stejné vstupy jen jedno je dostává o takt nebo 1.5 taktu zpožděné, první jádro pak zapisuje do paměti/okolního světa, jeho výstupy se ale ještě také zpozdí a pak se porovnají s druhým jádrem, pokud je rozdíl, tak se vyhlásí chyba a většinou je nastavené, že se systém zresetuje nebo odstaví a řízení převezme záložní nezávislý čip, třeba druhá LockStep dvojice. Toto je jedno z řešení pro bezpečnostně kritické systémy s vyššími požadavky (SIL).
Z architektur, které tuto techniku podporují a u kterých výrobci neprodávají kompilátory porušují licence a nebo minimálně jejich záměr, lze jmenovat Cortex-R4/5 (například TMS570, se kterou také pracujeme) a PowerPC (některé členy řady FreeScale/NXP MPC55xx). Dále jsou zde SPARCy, ale ty pro space dokonce implementují často majoritní logiku ze tří přímo na čipu.