RISC-V, podobne jako MIPS, nema zadne stavove bity. Ukazuje se, ze to zbytecne komplikuje procesor, namisto CMP+BZ staci BEQ apod., jedine, kde by se to dalo pouzit, jsou bitove posuny a pro ty zase neni obdobny prikaz ve vyssim programovacim jazyce (pouzivaji se sice, ale zase ne tak casto).
[Navic se priznakem nedetekuji ani preteceni, schvalne, jestli to nekomu chybi]
Samozřejmě že chybí, příznaky C,S se používají v C/C++ právě na detekci přetečení a podtečení, i když k nim není přímo přístup v jazyku, například když se napíše x < 0.
Problém nezpůsobují ani tak příznaky, ale jejich kombinování. Konkrétně instrukce která mění jenom některé z nich a některé ponechává na původní hodnotě a konkrétní příklad je x86 INC. Ty instrukce které mění všechny příznaky jsou bezproblémové a příznaky se pak chovají jako běžný registr.
Však tam je k dispozici BEQ, BNE (klasika), BLT, BGE pro signed a BLTU a BGEU pro unsigned (jak si to ALU vyřeší, není specifikováno, klidně ať si uvnitř dělá příznaky). Není tedy zapotřebí nastavovat příznaky podle CMP a hned potom je použít v J?? instrukci (to je to řešení x<0). Jak píšu, například pro víceslovní aritmetiku je to nepatrně komplikovanější, ale pro ni stejně není v C ekvivalent (ten byl kdysi v C--, takový jednoduchý jazyk pro reálný režim).
To není ono. Viz tento příklad:
x-=5;
if (x<0) foo();
další příklady:
if (a<5) foo(); else if (a>5) bar();
while (--count) baz();
Všude výše se udělá 1x ALU operace a pak se zadarmo recyklují výsledky v Jxx. Jak je vidět tak o tvrzení že příznaky jsou zbytečné by se dalo s úspěchem polemizovat.
Aha chapu. Ted se divam na vysledek generovany gcc. Bez optimalizaci na to kasle (udela si navic cmp):
subl $5, -8(%ebp)
cmpl $0, -8(%ebp)
jns .L5
call foo
S optimalizacemi dela cmp taky, ale lip (porovnava ne s nulou ale se 4 bez odecteni):
cmpl $4, %eax
jg .L5
movl $.LC0, (%esp)
call puts
Ten while:
movl %eax, %ebx
subl $1, %ebx
je .L4
(zajimave, kdysi bych tam hodil DEC :)
Nepripada mi to moc odlisne od SUBI+Branch popr. jeste SLT kdyz nekdo pise x = y < z apod. (http://www.root.cz/clanky/instrukcni-sada-procesorovych-jader-s-otevrenou-architekturou-risc-v/#k07)
Co míníš tou pokročilejší architekturou? Např. moderní x86 CPU si stejně překládají svůj kód na risc like microops. A v L1 cashi už mají přeloženej kód nikoliv x86. Takže v tomhle fakt rozdíl nebude. A většinou je slabý místo čekání na paměť ne na volnou ALU. Po přidání dalších ALU se výkon dneska už vo moc nezvedá.
Na RISCu je vetsinou pouzita klasicka pipeline, typicky: fetch instrukce, dekodovani, ALU operace, pristup do pameti a zapis vysledku, co jeden rez pipeline, to jeden takt. Co tam chcete urychlit pridanim nejakych priznaku? Ciste teoreticky odhad vysledku skoku, ale za ty komplikace to nestoji,
Ale jo, to vime, akorat s tim ted nejde (u Intelu) uz nic moc delat, protoze to vychazi z pozadavku z davne minulosti. Tehdy, jeste na stare dobre B0B0, se vyplatilo delat smycky pres dcr(dec)+jnz s tim, ze dec nemenila C (pozdeji na 8086 CF), protoze se mohl pouzit pro prenos informaci mezi iteracemi. Nejaky posun bitoveho pole napriklad. No ale ted se na tuto CISC architekturu ladujou RISCove vlastnosti a hned jsou tady flags stalls apod.
Jinak nemate uplne pravdu, protoze prave priznaky tam pridavaji dalsi zavilosti mezi instrukcemi, takze to ovlivni superskalarni zpracovani. Ostatne sam Intel na to ma silene dlouhy manual, co a jak se muze a nemuze parovat. Na cistem RISCu toto moc nehrozi, tam samozrejme zavislosti jsou taky, ovsem mezi registry, kterych je hafo (a tady je i jeden z duvodu, proc existuje zero registr a proc to neni to stejne, jako kdyz se jen rekne, ze napriklad r0 bude mit 0 protoze se tak dohodneme - neni to totez :-)
kvr_kvr: Ten kus kódu je před optimalizací nebo po optimalizaci? Nějak se mi to nezdá, neboť už Intel Core Microarchitecture/Sandy Bridge umí skrze makrofúzi pár instrukcí TESTL/JS sloučit do jedné jediné mikro-op (vida, něco jako ten v článku uvedený risc). Tady je to ovšem přerušené instrukcí MOVL REG, MEM, která by pro uplatnění makrofúze měla být před TESTL (Intel hovoří o tom, že pár TESTL/JS musí být "adjacent in the instruction stream"). Potom by celá sekvence byly dvě mikro-op v core (SUBL + TESTL/JS) bržděné LOADem (předcházející MOVL) a schopností jádra korektní predikce skoku (STORE běží paralelně).
ajo mas pravdu, tady to skutecne nema moc vyznam, tezko rict, proc to prekladac dela. Pro INC/DEC/shifty to intel doporucuje vkladat prave kvuli tem CISCo->RISCovym problemu x86(64):
xor eax, eax
mov ecx, a
sar ecx, 2
setz al ;SAR can update carry causing a stall
takze se kod ma natahnout:
xor eax, eax
mov ecx, a
sar ecx, 2
test ecx, ecx ; test always updates all flags
setz al ;No partial reg or flag stall,
Mozna se ani tvurcum prekladacu nedivim, ze jsou zmateni, cely ten ntel® 64 and IA-32 Architectures Optimization Reference Manual je plny ruznych vyjimek (vcetne zpusobu vytvoreni devitibajtove instrukce NOP :)
"(zajimave, kdysi bych tam hodil DEC :)"
Na P4 Prescott (CPUID 0F_2h) byly sub/add svižnější než dec/inc (souviselo to s přetaktováním pipeline). Od té doby gcc (a myslím, že i vcc a jiné) v překladu sahají po sub/add.
http://www.intel.com/assets/en_US/pdf/manual/248966.pdf
Jedno-bytové kódování INC a DEC není v 64-bit módu k dispozici. Opcode pro INC a DEC jednotlivých z osmi registrů je použité pro REX prefix (64-bit/32-bit, rozšíření výběru registrů na 16). Zároveň INC a DEC jsou operace problematické. Protože se dříve často používaly pro čítání průchodů a posun ukazatelů v cyklech realizujících víceslovní aritmetiku (např. 32 bit na 8086), tak nenastavují carry -> část příznakového registru je další závislostí. Kombinace SUB a pak podmíněný skok je také spíše pro reordering nevýhodou. Je tam závislost dvou blízkých a ještě k tomu ta druhá je skoková. Pokud se provede upravené porovnání proti předchozí hodnotě registru, je značná pravděpodobnost, že ta mohla být vzdálenější/starší instrukce a není potřeba čekat na výsledek, instrukce vlastního nepodmíněného odečtení se provede paralelně nebo dokonce mnohem později, kdy je její výsledek potřeba.
Zrovna možnost provádění této optimalizace kompilátorem je asi jednou z hlavních příčin, proč kompilátory začínají mnohem více trvat/využívat oblast nedefinovaného chování v normě C. Vyplývá z toho například, že není možné použít signed typ pro cyklickou aritmetiku. Ta je definovaná jen pro unsigned, operace x -= 5; if (x < 100) opravdu musí počítat s tím, že došlo k podtečení. Pro signed klidně provede větev v podmínce i pro 0x80000001 - 5.
Skutecne, modulo aritmetika plati v C a C++ jen pro unsigned typy, pro signed neni definovana, nicmene spousta programatoru pocita s tim, ze to projde tak, jak to umi treba i86. Coz je do budoucna dost spatne, asi kazdy si dokazeme predstavit ty skryte chyby, ktere mohou nastat (a i chovani pitome -1 pri posunech neni uplne skvele ;) - mimochodem taky neni definovano AFAIK.
Jak je přesně myšlen ten poslední odstavec? Jako INC, DEC je jasný, ale mnoho instrukcí vlastně nemá vůbec sémantiku na nastavení například S či C. Nějaký ten AND, OR, DAA, všechno to mění obsah registru přes ALU, ale S/C ztrácí význam (AFAIK).
Btw: pouze MOS 6502, můj oblíbený čip, nastavuje Z a N i při instrukci LOAD apod., což je zvláštní, ale zase konzistentní.
Myšleno je to takto:
Některé instrukce změní obsah VŠECH důležitých příznaků a ty jsou OK.
Některé instrukce změní obsah VYBRANÝCH příznaků a ty ostatní zůstávají nezměněny a to je problém, protože se tak vytvářejí závislosti bránící paralelizaci.
To že nějaký příznak nemá smysl a jsou v něm blbosti nevadí, důležité je aby ho instrukce nemusela zachovat.
To kodóvání délky instrukcí, skoro to vypadá, že se autoři inspirovali u UTF-8 :-)
Jinak by mě docela zajímalo, zda má nějaké výhody tříadresový kód. IMHO v 99% případů je většina (mezi)proměnných stejně temporary, takže těch dalších 5bitů navíc je plýtvání. Spíš by je šlo použít na komplikovanější adresace apod...
V mnoha případech je potřeba dále zachovat i obě vstupní hodnoty. Zkusím z hlavy například na klasické binární vyhledávání), key x18 (a0), array start x19 (a1), array size x20 (a2), návrat index x16 (v0), a uvidíme jak to vyjde (za chyby neručím, nekompiloval jsem to)
add x21, x0, x0 /* spodní limit */
loop:
beq x20, x21, not_found
add x16, x21, x20
srli x16, x16, 1 /* 1. 2op */
slli x22, x16, 2
add x22, x22, a1 /* 2. 2op */
lw x22, x22, 0
beq x18, x22, found
blt x18, x22, smaller
add x20, x16, x0 /* 3. 2op */
jal x0, loop
smaller:
add x21, x16, x0 /* 4. 2op */
jal x0, loop
not_found:
add x16, x9, -1
found:
jalr x0, x1
Tak zrovna tady je to tak půl na půl. Na druhou stranu přidání více instrukcí v případě dvouoperandového kódování povede na více souběžné řešení více závislostí a více problémů s přejmenováváním registrů a jejich plánování v superskalární implementaci, takže to buď výkon zpomalí nebo bude vyžadovat složitější HW.
Jinak si lze RISC-V také spustit v JavaScriptu
ale na rozdíl od jor1k se zdá, že přímo na webu není k dispozici image s GCC ani možnost si do běžícího simulátoru něco nahrát.
Já tam teď vidím dohromady dvě tříadresové:
add x16, x21, x20
slli x22, x16, 2
(chyba - měl být jednoduchý load immediate -1) add x16, x9, -1
Ta druhá by šla eliminovat, kdyby procesor uměl adresování s indexem, navíc by ušetřil i následující instrukci add. Obě by šly možná eliminovat, pokud by se místo indexu udržovaly start a end jako pointer.
Tak jako tak, je to s původním řešením 4:2.
Link vypadá pěkně, až na to, že je to jenom busybox. Ale na obranu je třeba říct, že s downloadováním image s gcc by asi přišli brzo na buben :-)
Chyby v chybě byl překlep v x0 na 9 místo na 0 a copy paste ADD místo ADDI.
Je pravda že zrovna tento kód není moc přesvědčivý. I přesto jsem ho do diskuze dal, i když moc argument nepodpoří. Celkově ale mám pocit, že se tříoparandové instrukce hodí v takovém počtu případů, že se vyplatí.