En wat bedoel je met LEA-truuks?
De x86 is bekend om truuks als:
lea eax,[eax+4*edx+4]
In Pascal-notatie:
a:=a+4*d+4;
... dat is best wel behoorlijk wat rekenwerk dat je in 1 instructie kunt stoppen...
De ARM spant wat dit betreft de kroon, want hij heeft niet alleen een barrelshifter bij indirecte adressering. Bovenstaande constructie zou op de ARM luiden:
adds R0,R0,R1,LSL #2,#1
Niet alleen zijn dezelfde truuks mogelijk, bij ARM kan je de barrelshifter toepassen op ieder instructie (zelfs op constanten), en kan je ook nog eens kiezen tussen naar links en naar rechts schuiven, en of je een logische of arithmetische shift wilt. Waar een x86 alleen maar 1, 2 of 4 bits kan schuiven, kan een ARM 31 posities schuiven in iedere instructie.
Maar daar houdt de complexiteit van de ARM niet op. Stel dat je in Pascal hebt:
if b<>0 then
..a:=a+4*d+4;
... in x86-machinetaal schrijf je:
or ebx,ebx
jz a1
lea eax,[eax+4*edx+4]
a1:
... in ARM-machinetaal:
eor r1,r1,r1
addsne R0,R0,R1,LSL #2,#1
... de conclusie is dat de instructies van de ARM voeren veel meer werk uit dan x86-instructies.
Een beroemd voorbeeld van de kracht van de ARM-instructieset is het berekenen van de grootste gemene deler tussen 2 getallen. In Pascal:
function gcd(x,y:integer):integer;
begin
..while x<>y do
....if x>y then
......y:=y-x
....else
......y:=y-x;
..gcd:=x;
end;
Hoe ziet zoiets eruit in ARM-machinetaal?
a1:
cmp r0,r1
subgt r0,r0,r1
sublr r1,r1,r0
bne a1
Probeer het maar eens in x86, je krijgt een veelvoud aan code. Er wordt hier handig gebruik gemaakt van het feit dat de ARM voorwaardelijke aftrekinstructies heeft (bij de ARM kan alles voorwoordelijk). Geen enkele andere processor heeft voorwaardelijke aftrekinstructies, bijgevolg is de ARM-code een fractie van die van andere processoren.
Dit is overigens een belangrijk deel van het geheim van de energiezuinigheid van de ARM: Je hebt aanzienlijk minder instructies nodig om het probleem op te lossen, dus minder megahertzen, dus minder stroom.
Wel interessant allemaal, alleen tegenwoordig worden de aanduidingen RISC en CISC meer gebruikt om de processor architectuur te karakteriseren dan de instructieset (in tegenstelling tot wat er origineel met die termen bedoeld werd).
Een ARM is een RISC CPU niet omdat ie zo weinig instructies heeft, of omdat instructies zo eenvoudig zijn, maar omdat de architectuur zo is opgezet dat je met heel weinig hardware (transistors) de hele instructieset kunt implementeren (decode en dispatch bedoel ik dan). Juist om die reden is de ARM instructieset kort maar krachtig en kan je hele weirde shit in heel weinig instructies, maar is tegelijkertijd de manier waarop de instructieset is opgezet bewust heel generiek/orthogonaal: bijna alle instructies ondersteunen precies dezelfde grappen, zoals het gebruik van barrel shifters enzo, van de conditie flags, etc. Bij het uitvoeren van de instructies kan dus heel veel van de hardware op de CPU worden hergebruikt voor meerdere instructies. Tevens is de ARM instructieset nog steeds een typische (RISC) load-store ISA, itt x86 wat een typische (CISC) load-compute/load-compute-store ISA is. Instructies als "add [eax], ebx" (load adres [eax], add ebx, store op adres [eax]) kom je bij ARM dus nergens tegen, alle instructies behalve de load/store instructies werken enkel en alleen op registers. Ik ben geen ARM expert dus dit weet ik niet 100% zeker, maar ik vermoed dat je op ARM zelfs niet eens constanten in de instructie kunt encoderen, maar dat daar nog constant of ROM tables voor nodig zijn zoals op veel embedded CPU's (waarschijnlijk weet je dat zelf beter dan ik).
Pentiums kan je op dezelfde manier vanaf de pentium-pro ook als 'RISC chips' zien, ook al gebruiken ze de x86 ISA. De meeste (alle?) moderne x86 CPU's hakken instructies direct bij binnenkomst op in micro-ops (processor specifieke interne instructies) en voeren die vervolgens uit. De oorspronkelijke definities van CISC en RISC betekenen niet precies meer hetzelfde als dat ze initieel deden, wat denk ik vooral komt omdat zelfs de simpelste CPU's tegenwoordig al zo veel complexer zijn en zo veel krachtigere instructiesets hebben dat de originele betekenis van RISC (minder instructies, simpelere instructies, goedkoper en sneller te ontwerpen en produceren, minder transistors) op geen enkele moderne CPU meer van toepassing is.
[Reactie gewijzigd door johnbetonschaar op zaterdag 9 januari 2010 14:29]
Ik ben geen ARM expert dus dit weet ik niet 100% zeker, maar ik vermoed dat je op ARM zelfs niet eens constanten in de instructie kunt encoderen,
Als je doelt op de volgende constructie:
Pascal:
R1 := 1;
R2 := 3;
Dan is dat (met enige beperkingen) gewoon mogelijk:
MOV R1, #1
MOV R2, #3
Alleen als je met heel erg grote getallen gaat werken, dan moet je soms met meerdere instructies gaan werken om dit te bewerkstelligen...
edit:
@dmantione - dat bedoel ik dus, dat 65537 niet met een instructie in een register gezet kan worden
[Reactie gewijzigd door Little Penguin op zaterdag 9 januari 2010 17:51]
Maakt dit echter de processor een non-RISC CPU? Moet bij een RISC CPU iedere instructie slechts 1 taak gelijkertijd uitvoeren?
Wat zijn de kenmerkten van een RISC-processor?
- Load/store-architectuur
- Pipelining
- Geen microcode
- Kleine, homogene instructieset
- Grote, homogene registerset
De ARM-processor voldoet in zo'n beetje alle punten aan de criteria, dat de instructieset zodanig opgezet is dat deze erg krachtig is maakt 'm nog niet complex als in 'Complex Instruction Set Computing'.
a1:
cmp r0,r1
subgt r0,r0,r1
sublr r1,r1,r0
bne a1
In mijn opinie is het meer een feit dat de
bedenker van de instructieset een genie is, dat dit allemaal kan. Juist het feit dat men dus
alle instructies conditioneel gemaakt heeft, dat heeft tot gevolg dat men even krachtig of zelfs nog krachtiger is dan een CISC-CPU.
Hetzelfde gaat op voor de barrel-shifter, een betrekkelijk simpele toevoeging (op zich zelf). Maar dat 't vrijwel overal toepasbaar is, dat maakt 't een krachtig hulpmiddel.
Zo te zien weet je waar je over praat, maar:
je ADD[S] met 5 operands ken ik niet. Welke ARM versie ondersteunt dat?
De flexibele operand kan bij mijn weten: Rm{, shift} / of #immediate zijn, dus wat doet jouw R1,LSL #2,#1?
Ook deze code slaat nergens op:
eor r1,r1,r1 // zet R1 op 0 [zonder het status register aan te passen] (?)
addsne R0,R0,R1,LSL #2,#1 // een instructie die niet kan voor zover ik weet en je vorige instructie deed niets met het status register dus die NE conditie werkt dan nogal 'random', R1 staat nu op 0.
Je bedoelde waarschijnlijk gewoon:
CMP R1, #0 // b <> 0?
ADDNE R0,R0,R3,LSL #2 // a += d * 4
ADDNE R0,R0,#4 // a += 4
(volgens mij heb je dus 2 ARM instructies nodig om die x86 LEA te evenaren, maar ik gebruik vnl. ARMv5)
SUBLR ken ik trouwens ook niet. Mogelijke condition flags zijn:
EQ Z set Equal
NE Z clear Not equal
CS or HS C set Higher or same (unsigned >= )
CC or LO C clear Lower (unsigned < )
MI N set Negative
PL N clear Positive or zero
VS V set Overflow
VC V clear No overflow
HI C set and Z clear Higher (unsigned >)
LS C clear or Z set Lower or same (unsigned <=)
GE N and V the same Signed >=
LT N and V differ Signed <
GT Z clear, N and V the same Signed >
LE Z set, N and V differ Signed <=
AL Any Always. This suffix is normally omitted.
Ook een mooi voorbeeld van de kracht van bepaalde ARM instructies:
LDM (load multiple) en STM (store multiple)
vb.:
LDMDBNE R1!,{R2-R4, R8}
Dit betekent zoiets als:
LoaD Multiple registers, addressing mode 'Decrease source Before read', execute this instruction when status flags say: Not Equal, source address is stored in R1, write back (!) updated source address. Destination registers are R2, R3, R4 and R8.