RISC instruction sets I have known and disliked
blog.jwhitham.org
blog.jwhitham.org
Other aspects of the architecture were truly strange. There were no load or store instructions. They were a side effect of setting an address register. I was the lead developer for two of the PL/I compilers for the 6600. Much fun. For those interested in strange architectures, I recommend the Wikipedia article https://en.wikipedia.org/wiki/CDC_6600.
No. Not true at all. Not even close. CISC instructions are expected to be complex, hence the name. MC68K was pretty regular, NS32032 highly regular, both CISC.
RISC are reduced, not regular, so for example you'd expect memory access to only occur with specific memory access instructions, whereas all arithmetic and other computation only deals with registers.
So CISC was usually more regular, not less. x86 is the exception, because it was just extensions heaped on top of extensions: 8080 8 bit -> 8086 segmented 16 bit + 20 bit addresses -> 80286 protected 16 bit segmented with 24 bit addresses -> 80386 semi-segmented/mostly flat 32/32 bit, etc.
Given the advance of modern computer architecture that the author talks about many of the old advantages of RISC no longer apply. If you're going to be doing out of order execution the the extra effort in implementing some extra instructions really isn't important for application processors.
The big advantage that RISC has these days is that fixed width instructions are easy on the decoder. You can also have variable width instruction that use UTF8esque byte marking to make things easier on the decoder but x86 doesn't have anything like that. But then again separating them entirely in the ISA makes thing easier on the designers.
Oh, and it's a bad idea to touch memory multiple times in a single instruction on a modern machine but Intel's optimization manuals warn you not to do that and compilers abide by those warnings. If you want an ISA feature that's really hard to design into a high performance uArch then there's indirect addressing but unlike most CISC ISAs x86 managed to avoid that one.
The advantages of RISC might be overblown in some sense, but there have been a lot of new instruction set architectures developed over the last 20 years when the RISC/CISC debates were raging. Many of those have been weird in various ways but almost all look a lot more like RISC instruction sets than CISC instruction sets and it's not just because people are following the herd.
And I've got the sense that my inside view of the issue is underestimating now advantageous RISCishness is. When ARM had the opportunity to redesign their ISA when the transitioned to 64 bits they simplified their ISA quite a bit and increased the number of registers from 16 to 32, basically making their ISA much more similar to a classical RISC design. I don't really understand why they thought that going that way was an advantage but it seems like people actually involved with designing these things instead of just thinking about them in their armchairs still think that RISC has a lot of advantages.
One thing that I've wondered is how much more efforts are needed to decode variable-width instructions. Decoding itself sounds fairly easy (but frankly I don't know any details), to the point that the amount of time needed for loading/storing/calculating overwhelms that of decoding. But decoding should happen extremely fast to fill the pipeline, so the speed might still matter. Can decoding instructions be an actual bottleneck?
Yes. We have processors which can execute 4 or more instructions in parallel (if I'm reading http://www.anandtech.com/show/6355/intels-haswell-architectu... right, the processor I'm using to type this message can start the execution of up to 8 microinstructions in parallel). You need to decode the instructions fast enough to keep up.
Since the clock is the same, you basically need several decoders in parallel. But with variable-length instructions, you have to know the length of the first instruction so the second decoder knows where to start; you have to know the length of both instructions so the third decoder knows where to start; and so on. The x86 architecture is a worst-case of a variable-length architecture: take a look at http://wiki.osdev.org/X86-64_Instruction_Encoding and think how you would determine the length of an arbitrary instruction.
High-performance x86 implementations have to do all kinds of crazy tricks. An extra pipeline stage solely to figure out the instruction lengths (see http://www.anandtech.com/show/6355/intels-haswell-architectu...), extra tags in the instruction cache to mark the instruction boundaries, decoding the instruction lengths while loading the instruction cache, caching already decoded instructions, and so on.
Contrast this with for instance RISC-V with the compressed instructions extension, where you have to examine just two bits on each instruction to figure out if it's a 32-bit or a 16-bit instruction. I'd have to look up the encoding for Thumb-2, but I'd expect it to be something equally simple. Make it simple enough, and you might be able to split the instructions and decode them in the same pipeline stage.
I'm guessing you haven't seen VAX. The first byte isn't even organised in any discernable pattern so there are both rare and very common instructions there, and operands are specified using a very flexible system that makes length decoding far more difficult than x86.
In contrast, x86 has a mostly consistent 2-3-3 octal-based encoding, and having the first 2-3 bytes is usually enough to decode the instruction's length:
Aside from the CASE statement (yes, the VAX has a table jump instruction) that can be (if I calculated correctly) up to 65,558 bytes in size, the next longest instruction are the six-operand ones that can be (again, if I calculated correctly) 43 bytes in size. Two operand register-to-register operations (and some indexed-register operations) take 3 bytes (1 for opcode, one for each register). Once you get used to it, it's pretty easy to read the actual binary code.
In the X86, it is possible for an instruction to be from 1 to 15 bytes long. (Maybe more today? It was 15 when I cared.) All you can tell from looking at the first byte is that it is either one byte longer than one byte. All you can tell from the 2nd byte is that it is either 2 bytes or longer than 2 bytes, and so on. When you walk all the way out to the 15th byte, you might find a MOD/RM field, which may contain invalid combinations. Finally you have enough information to raise (or not) the illegal instruction exception. That is one very nasty equation.
Just one example of how variable instructions can become annoying to a logic designer. OTOH, some machines are very regular in how instruction length is specified -- in IBM 370 code, for instance, you can look at the first 2 bits and know the instruction width. X86 is an example of organic accumulation of features over time leading to a large collection of special cases.
X86 allows you to store into code, even immediately ahead of execution. This made sense in the 1970s when Harry Pyle designed the instruction set and CPUs were slower than memory. Superscaler CPUs have to support this. But, since almost nobody does that any more, they don't do so efficiently. Storing into code near execution causes an exception event, flushing all the superscalar lookahead and backing up to just before the instruction doing the store into code. Then the code gets modified, and the pipeline reloads, having lost tens to hundreds of cycles.
Actually, it's a lot simpler than I expected!
And when balancing a CPU core you really want to make the front end wider than your execution resources would require so that you recover from branch mispredicts quickly and refill your various OoO buffers fast. x86 processors tend to do this less than, e.g., POWER because x86 decode is expensive.
I don't see this. Do you have any examples? x86 was only "nicer" for the first 10 hours of assembly—and if you're writing your own (dis)assembler, it's about 10x harder than any risc. It's also much harder to compute cycle time, which is the only reason I can think of to be staring at assembly for multiple hours (as opposed to e.g. c).
It's also much harder to get "good" at x86, and it mostly consists of learning the subset of x86 that is actually optimized in the way you might expect. The vast majority of instructions you shouldn't be using at all.
Sure, it took them a bit to be convinced that single-byte loads and stores were worthy of dedicated instructions.
It was designed so that most of your kernel code wasn't actually running in a privileged CPU mode, but instead make upcalls to PAL code, a sort of super lightweight hypervisor that emulated how ever many of rings of protection the kernel needed. (Ultrix and Linux needed 2 rings. VMS ran on another set of firmware that emulated more rings.)
It was a nice clean design that was running at 500 MHz back when Intel could manage 200 MHz. Its memory model was more friendly to parallel execution (and less friendly to compiler and JIT writers) than the x86 memory model, forcing weaker consistency guarantees out of the JVM memory model as a result.
It seems a shame to me that the architecture was never revived. I'd like to hear more about its quirks and flaws.
https://blogs.msdn.microsoft.com/oldnewthing/20040119-00/?p=...
https://blogs.msdn.microsoft.com/oldnewthing/20150805-00/?p=...
See also: http://free-electrons.com/blog/how-we-found-that-the-linux-n...
See also: http://robertoconcerto.blogspot.ca/2013/03/my-hardest-bug.ht...
Other architectures have the R prefix as an intrinsic part of the syntax, so if you write R2 in a slot where the instruction requires an immediate, you'll just get an error. If PowerPC did that, you'd still need to remember the right inline assembly constraint letter for GCC, but getting it wrong would 'just' result in an unpredictable compile error when the compiler decided to use r0, not silent misbehavior.
However that doesn't fix the gotcha with r0 being special, that is specified in the ISA. In fact it's that way precisely so you can load an immediate without needing a separate opcode.
It would still be a gotcha, but a pretty minor one if messing it up just resulted in an error. I suppose the approach taken by AArch64 and others is preferable, where one register is just completely reserved as constant 0 rather than only in some encodings.
Yes, MIPS assembly is terrible, though I hear that the newer ISA revisions are better (the one I worked with most heavily was the 5k, and systems programming on it was very unpleasant).
Power and MIPS also both now have thumb-2 alike instruction encodings, POWER VLE and MIPS16e, respectively. It turns out that code size matters.
Lastly, RISC no longer means what it used to. It basically is used today to just mean a load-store architecture, as you now have variable-length instructions, multi-cycle arithmetic instructions, out-of-order superscalar chips labled "RISC"
When IBM came out with the POWER ISA, I seem to recall that one of the authors of the abacus book claimed it wasn't simple enough to be RISC, which is a quaint thought these days.
I don't blame MIPs from doing delayed branching etc because if most machine code is compiled from something else, and if most compilers use decent abstractions, one should be changing ISAs all the time to adapt and bolster the latest and greatest implementation techniques. (e.g. for out-of-order super scalar, its probably best to give the CPU some sort of dependency graph.)
The focus on hand-coding as a way to get to know the architecture, on the other hand, borderline insinuates that instructions sets should optimize for hand-coding, which is just plainly ridiculous.
Very good point on the `REP MOVS` front (and cool story!). Indeed if the Mill pans out it would instantly usher the renaissance for branch delay.
So yeah, I rather recompile than try to predict future architecture trends, but either way, the grossness should be there for performance not hand-coding ease.
x86's terrible reputation is well deserved. x86-64 fixes a number of problems, but for most of x86's lifetime we've had to live with 8 registers and the lack of IP-relative addressing...
Let's not forget how awful x87 floating point was, either!
But it looks like it's x86 plus SIMD. If so, then Intel probably has literally 1000's of man-years of validation suites for x86. If you remove some of the x86 instructions, how much of that test code do you break?
The Larrabee designers probably wanted to focus on the SIMD and on putting bunches of cores onto a single die. They didn't want to reimplement (or even fuss with) the x86 part. That's not what they were interested in.
Leaving x86 alone means the chip runs Windows, Linux, etc w/o any further effort. Or does it, like I said I don't know the architecture?
What do you gain by breaking that? It's probably a very small part of the silicon area anyway.
On the other hand, one of the things I like about x87 is that it's extremely dense because it's stack-based and RPN-ish. Here's 256 bytes of x87 awesomeness: http://www.pouet.net/prod.php?which=53816
PIC can be emitted on older x86 machines without RIP-relative addressing, but the code is larger and slower. As an example, consider -m32 gcc output for the C program
int x;
int getx() {
return x;
}
With no PIC: getx:
movl x, %eax
ret
With PIC: getx:
call __x86.get_pc_thunk.cx
addl $_GLOBAL_OFFSET_TABLE_, %ecx
movl x@GOT(%ecx), %eax
movl (%eax), %eax
ret
And with -m64, which emits PIC and uses x86-64s RIP-relative addressing: getx:
movl x(%rip), %eax
ret
Hopefully that makes the motivation clear.He neglects to mention the ARM's clever approach to this:
https://alisdair.mcdiarmid.org/arm-immediate-value-encoding/
This made me wonder how common such constants are. So I grabbed some code I've been working on recently, for which I happened to have assembly language output, and searched for every immediate constant. (This must be the first time I've found a good use for gcc's nasty AT&T x64 syntax.)
My code targets x64, so take the "analysis" with a pinch of salt. Out of the 8982 instructions that had immediate operands, there were 819 unique 32-bit constants. 762 (93%) were high-halfword or low-halfword only, so they could have been loaded with one halfword instruction. By comparison, only 392 (48%) could been loaded with one instruction on ARM.
Ten constants were better for ARM, in that they would take two halfword instructions to form, but only one MOV or MVN: ['0x00ffffff', '0x03ffffff', '0x0fffffff', '0x3fffffff', '0x7fffffe8', '0x7ffffffe', '0x7fffffff', '0x80000003', '0xfffffffe', '0xffffffff']. These ten constants were used by 84 instructions out of the 8982.
For a full 32 bit value prior to movw/movt you'd most likely load it from a constant pool rather than do a 4 insn sequence. (Some 32 bit values can be done with clever choice of 8imm sequences -- there's an algorithm you can use as a compiler to say "given this value can I create it in 3 or less insns?", which is worth the effort if you're targeting a pre-movw ARM cpu.)
So true... I work with the LEON (GPL implementation of SPARC) and have the occasional very bad day of SPARC asm code.
x86 is full of legacy single-byte instructions and complicated prefixes, hurting space efficiency and making it a huge pain to have high-throughput decoding. You could do a lot better if you took the x86 instruction list and reassigned all the encodings.
> The x86 system for storing 32-bit/64-bit immediates is much nicer. They just follow the instruction, which is possible because the instruction length is variable. Variable-length instructions are not usually seen in RISC, the Thumb-2 instruction set being the only exception that I know of.
Hybrid way, that could be the best of both worlds : https://github.com/trillek-team/trillek-computer/blob/master...
On a few words, it uses a bit to know if the literal would be bigger that could normally stored on a instructions of 4 bytes. If it's true, the next 4 bytes is the literal value.
Microsoft's C compilers do a much better job with inline asm by being more conservative in how they allocate registers around the asm (https://msdn.microsoft.com/en-us/library/k1a8ss06.aspx).
CodeWarrior's PPC compilers remain the ne plus ultra of inline assembly ergonomics, and it's a goddamn shame that LLVM has chosen (for pragmatic reasons) to follow GCC's mediocre lead rather than pursue something akin to it.
edit: this doesn't seem well-documented, if true. The Clang site mostly just talks about GCC compatibility, eg) http://clang.llvm.org/compatibility.html#inline-asm
ld r1, @r2 ; r1 = *r2
add r3,r1,r4 ; r3 = r1+r4
wouldn't set r3=*r2+r4 because the memory access hasn't finished by the time the add runs.And to give you an example of how you had to calculate things:
There were, if my memory serves me correctly 6 pipeline stages on the 5k numbered 0-5, plus instruction prefetch which was numbered -1. You subtracted the stage in which the instruction took effect from the stage in which a subsequent instruction needed to see that effect, and the result was the number of intermediate stages that all ALUs would need to go through.
Worst case scenario would be if you were modifying RAM that would be read as an instruction; it wouldn't take effect until stage 5, and instruction prefetch was stage -1 so you needed to make sure all ALUs were busy for 6 clock cycles. In theory you could do the math to figure out the scheduling for each ALU, but I just dropped 6 SSNOPs in there, since it was a code path that was only hit during loading of a new process, 6 wasted clock cycles was not a concern.
Note that this is unrelated to interlocks, as any Modified Harvard Architecture will require some sort off synchronization when changing the instruction stream. However, most ISAs have a single instruction that stalls the pipeline and discards any prefetched instructions (e.g. isync on Power). They added one in later revisions of the MIPS ISA as well.
Another fun thing was that there was no interrupt-safe way to disable interrupts, as the interrupt-enabled bit was in a word-sized register along with other values that could legitimately be changed by an ISR. This was also fixed by later revisions of the ISA.
Both load delay slots and branch delay slots are allowing the microarchitecture (a simple 3-stage pipeline) to dictate architecture, which is a classic way to store up pain for the future.
Or else use a search engine to find sources like this: https://books.google.com.pe/books?id=LL52JBPU4CwC&pg=PA52
The article's author should review recent documentation on the MIPS architecture. I think that the paragraph that starts with the phrase "MIPS is the worst offender" seems ludicrous for people working with modern MIPS implementations (let's say post-1992!).
But yeah, would be nice to get a take on RISC-V in context of this rant.
RISC-V with the compressed extension is incredibly efficient in its encoding. Better than x86 or ARM in both static and dynamic bytes per program.
Also, Icache pressure is a huge problem in modern warehouse-scale computers.
Any processor that cares about performance will almost certainly be implementing the C extension to RISC-V. It also enables more efficient macro-op fusion, turning common two instruction 4-byte idioms into a single, more powerful instruction.
Everyone else seems to first mention their cool branch predictor, or vector processor.
; input 1 (msb r1, lsb r2)
; input 2 (r3, r4)
; output (r5, r6)
xori r5, r4, -1
sltu r5, r5, r2
add r6, r4, r2
add r5, r5, r3
add r5, r5, r1
This is what I mean. Outside a few applications (mostly asymmetric crypto) nobody cares that it takes five instructions instead of two. Remember that this is the same processor that outright omits multiplication from the core spec.