IMHO, a memory-to-memory architecture would make for a much simpler ISA and allow much easier code generation (no register allocation needed).
IMHO, a memory-to-memory architecture would make for a much simpler ISA and allow much easier code generation (no register allocation needed).
"Waterman shows that RVC fetches 25%-30% fewer instruction bits, which reduces instruction cache misses by 20%-25%, or roughly the same performance impact as doubling the instruction cache size."
Larger caches are slower, so just doubling the cache size doesn't necessarily recover the performance lost by larger instructions.
...which makes me wonder why they didn't just make that the standard. x86 is dense and that's partly where its performance comes from.
The base RISC-V was designed to be minimal; for instance, hardware multiply and divide is the M extension. I expect that, once the C (compressed instructions) extension is standardized, most high-performance implementations will include it, since the extra area cost should be minimal compared to the out-of-order machinery; small in-order implementations where area is at a premium and performance is not so important will continue to use only the base 32-bit instructions. There's even a variant, RV32E, which omits half the registers to make the core even smaller.
And not everybody wants to go through the design effort to support variable-length encoding (it's non-trivial and can potentially increase pipeline latency to support it). It very well could become a de facto RISC-V standard, but that remains to be seen.
Instruction fetching and parsing is an important bidimensional bottleneck that does restrict your architecture. It's not a bottleneck for run-time performance because it's predictable and optimized at design time.
Basically, instruction operands are divided into 4 classes:
- G (global): the operand refers to the global memory space and contains an offset and size.
- A (automatic): the operand refers to the current stack frame. The offset is relative to the current base pointer.
- I (immediate)
- T (temporary): this is where intermediate results from computations are stored. For example, the result of `a + b` from `(a + b) * c` would be stored in the T memory space. Upon the end of a statement in the HLL, all values in the temporary space are discarded.
Reference: https://github.com/bbu/quaint-lang/blob/master/src/exec.c
Did you mean stack pointer ? base pointer gets set once where as stack pointer changes between every function call.
> Here is how I implemented a small instruction set for the VM of my experimental language, without using GPRs.
stack pointer is still necessery unless some memory region is predefined as stack pointer.
'A class' can be made more space efficient by dividing stack into 16 chunks and then instruction would only need 4bit to address.
[1] pg 12, para 5.
You MUST worry about registers due to the exponential increase in latencies between registers and larger memory communicating with the ALU. Also, limited are the amount of execution units.
If all memory accesses cost the same in terms of latency, then yes, registers would be an implementation detail.
Think of how x86-64's 16 architectural registers are dynamically renamed to more than 120 physical registers.
I would think it's hard to beat the compiler though (when it knows how many GP registers there are). Consider register pressure. If the compiler assumes there are infinite GP registers (because they're hidden now, no longer an implementation detail), it can generate code that spends more time spilling and reloading registers than computing.
The compiler has seen the code before; it knows how many variables it will need to work with an how to schedule available resources. The processor (relatively) simply is executing a stream of instructions. It would have to look pretty far ahead (VLIW) or have sufficient execution units (superscalar) to have high throughput and not get bogged down spilling.
But, if the finite amount of resources, like number of processing units or GPRs become very high relative to the amount of symbolic variables, then I think the register names could be hidden.
But compilers don't know this! They know how many registers there are in the ISA, but not how many there really are in the physical architecture.
Physical architectures map ISA register names into register file locations. It's an abstraction! You can really use a register more than once at the same time because they will be renamed. Your parent is suggesting that we read and write from memory as normal, and just map memory locations into the register file, rather than ISA register names.
Registers may be more of an abstraction than you realise.
(Maybe compilers do know about how large the register rename buffer really is on certain physical chips and take this into account when allocating them. Sounds like something Intel's C compiler might do. I'm not an expert.)
What do you mean by this?
Do you realize register renaming is only done for instruction scheduling purposes, to avoid bubbles in the pipeline?
addl %ebx, %eax
movl %eax, foo(%rip)
addl %ecx, %eax
movl %eax, bar(%rip)
Then the second add can start while the first and the first mov are still running, because the logical reuse of eax is independent of the first and so will receive a different entry in the rename buffer.Happy to be confirmed or corrected by someone who knows more.
I'm a big fan of the Mill's idea of a specialized compiler that reencodes a general code into your architecture. It solves this problem, allows for JIT optimization of compiled languages, and is a nice portability layer available for virtualizing other architectures.
It also adds a nice layer for malware to hide, but well, we can't have everything.
the obvious penalty is the push/pop pair around each function call. as a first optimization pass, skip the pop when the pushed values aren't used - if a register isn't touched, you can get away with this. it's damn fast, because the push can rely on l1 cache. there's no stall when the processor promises to write that out eventually. you don't care till you need to do the corresponding pop. i had plans to skip the push , depending on usage, but didn't quite get that far.
anyway, the point is, just using registers as the top of the stack can get pretty good results with just a few man-months of effort.
Only operate on RAM, let the processor figure loads and stores and register utilization as needed. I suspect leaving these decisions to the software as opposed to the hardware is less prone to bugs, obsolescence and whathaveyou.
The performance advantage isn't clear. It would require optimizations to the instruction coding and the memory access schemes, because plain 64 bit addresses take more space than register codes.
Stack Machines oppose Register Machines. Surely, register machines can emulate stack machines, but I suppose that would incur overhead.
Chaching is not a transparent implementation detail of the processor, i.e. compilers and assembler programmers do take caching into account, as far as I can tell from reading HN. Register utilization is a concern as well. Register renaming is probably just a implementation detail, I wouldn't really know.