x86 is Turing-complete with no registers (2014)
mainisusuallyafunction.blogspot.com
mainisusuallyafunction.blogspot.com
Consider an instruction with four fields: A B C D. Words and addresses are normal two's-complement integers. Every cycle, the machine takes the value at address A, subtracts the value at address B, and stores it in address C. If the result of this, as a signed integer, is 0 or negative, it jumps to address D. Otherwise it take the next instruction. Using temporary values in RAM, self-modifying code and some constants, everything can be implemented with this single instruction. And a lot of it surprisingly efficiently. Some assembly-style syntax for such a machine:
A B C D
#0 #0 tmp destination
Jump to destination. 0 - 0 = 0, so jump to D. The # is syntactic sugar for an immediate literal. The value is placed in an address and that address is used. Tmp is a memory address, a temporary scratch value. In my made-up syntax here this is implicit if there's no 4th field given: // move is simple
src #0 dst
// subtract
srca srcb dst
// negate
#0 src dst
Addition is an exercise left for the reader :) Let's use * as the current address in the program, known at compile/assembly time. We can do a cheap subroutine call: #0 (*+3) retaddress subroutine
Negative of the return address of the next instruction is in retaddress. Result will always be negative for sensible addresses, so jump to subroutine. To return, just write the negative again back to the D field of an instruction: #0 retaddress (*+5)
#0 #0 tmp tmp // last field will now have return address, jump
Of course, writing self-modifying code without checking it is guaranteed to not work. I probably got the offsets and stuff wrong. But hopefully it gives the idea. Doing the same with the address fields lets you do arbitrary indirection and address calls and make a stack. Often 2 - 5 instructions. Everything a typical RISC machine can do can be done in 1 - 30 instructions. Except multiply, divide, and bitwise boolean operations on the whole word, because you have test and branch on each bit in an inconvenient way. I'm sure something more efficient is possible, but it's quite an improvement on the classic subleq code density.One-instruction set computer: https://esolangs.org/wiki/OISC
I'll have a full look through the blog later but just the summary of the code density results is very interesting (at http://retroramblings.net/?p=1414).
Do you have a gut feel for what is happening here and why the compressed 32 RISC ISAs do so well compared to yours?
> As others have shown, we can compute using alphanumeric machine code[1] or English sentences[2], using only the mov instruction[3], or using the MMU[4] as it handles a never-ending double-fault. Here is my contribution to this genre of Turing tarpit: x86 is Turing-complete with no registers.
[1] http://www.phrack.org/issues.html?issue=57&id=15#article
[2] http://www.cs.jhu.edu/~sam/ccs243-mason.pdf
> so memory is (effectively) an extremely large register bank.
This was true-ish however in the late 70's/early 80's/1Mhz CPU days - but registers were always better even if slightly. The 6502, for example, could load the X register with an immediate value in 2 cycles, or from zero page (first 256 bytes of RAM) in 3 cycles, or from an arbitrary 16-bit address in 4 cycles. The few register-to-register operations all work in 2 cycles. (Then you have the TMS9900 that actually did use RAM as registers - only having 3 - one for the program counter, one for the status register, and a "workspace pointer" that told the CPU where the fake 'registers' lived.)
Of course, x86 has elaborate caching mechanisms to help. Your freezer is still in your kitchen (cache), but you still have limited space you actually use to do work (countertop).
What RAM is basically a superfast I/O device (which is why memory-mapped I/O is a thing). It's funny that the IBM mainframe for RAM - "storage" - kinda made more and more sense the more that RAM speed diverged from CPU speed.
You obviously shouldn’t use RAM as a naive replacement for GPRs in normal code (unless the goal is extremely fast JIT compilation where minimizing spills is not important).
https://en.m.wikipedia.org/wiki/Register%E2%80%93memory_arch...
LS is generally found in RISC machines and is also generally considered far easier to implement pipelining and ILP. Fewer and often simpler Memory Modes, more obvious or simpler to deduce data dependencies. But it often takes a few more instructions to complete an algorithm or even a simple expression. Less code density.
https://en.m.wikipedia.org/wiki/Load%E2%80%93store_architect...
There’s also stack machines which approximate math well especially if you like RPN/prefix notation. They just tends to make logical constructs like loops look unfamiliar when compared to math or typical/popular programming languages. So it makes programming easy, compiling easy since it’s relatively natural to analyze & compile expressions. Also, instructions can be extremely compact such as single bytes.
https://en.m.wikipedia.org/wiki/Stack_machine
Another slightly related take was VLIW. You take snippets of instructions (Basic Blocks in PL) that are known to fit certain pipelining or data dependencies rules. Theses blocks of code can then be executed concurrently since you have well established criteria. However making compilers able to predict when blocks should execute in the presence of a complex single processor turned out to make writing the compilers very complex.
If memory serves, the compilers never really caught on. The assumption was that they would eventually figure out how to leverage the explicit parallelism efficiently, but it never materialized as a performant solution for most problems.
x86 is Turing-complete with no registers - https://news.ycombinator.com/item?id=7224061 - Feb 2014 (23 comments)
ff242500004000 jmp qword [0x400000]
How is this "conditional"? The article explains that it simply "jumps to whatever address is stored as a 64-bit quantity at address 0x400000", but where's the condition here?Create a 2-element array of branch targets. Compute a boolean condition function (0 or 1). Index into the 2-element array with this 0 or 1 value and write it into 0x400000, then jmp qword [0x400000].
In any architecture, including x86, every memory access is an ALU load or store, and our task would be completely impossible.
>"But x86 does not have this property."
The x86 does have this property(!) -- but it is hidden/abstracted away from the programmer -- by the use of x86 microcode between x86 instructions -- and the ALU...
But -- an interesting and educational article nonetheless...
All x86 registers -- are basically just abstracted views of an ALU and/or Memory (both possibilities are merged into one "view", depending on which CPU operation happened last!) -- which is now may be cached by the CPU (as opposed to being a direct view of the ALU) -- at a given CPU/ALU state/time...(!)
https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...
I see this a lot on HN. Any comment has different ways to be interpreted. It’s very frustrating when someone picks, out of the many different possible interpretations, an interpretation that allows them to argue against it.
It’s also highly cultural and differs depending on context. Something which is nice in one culture or context could be outright offensive in a different culture or context. Even if you are not traveling to another country, you can be shocked by the different ideas about how people should behave if you travel.
For example, someone from California could come to New York. Both are coastal, wealthy states in the US that vote Democratic. The Californian will think, “Geez, New Yorkers are so rude! They don’t say ‘hi‘ or ‘please excuse me’ or ‘thank you’.” Meanwhile, someone from New York goes to California and thinks, “Geez, Californians are so rude! They are passive aggressive, get in your way, and waste your time!”
It’s really just a difference of ideas about how people should behave. New Yorkers aren’t rude, despite the reputation. Californians aren’t rude either. They just have different ideas about what “rude” means. Californians will say all sorts of nice things to you to try and make you feel comfortable. New Yorkers will help you carry your luggage up the subway stairs without saying a word. Different worlds, different ways of behaving.
Any effort to moderate a forum like HN is going to run face-first into the problem of enforcing one set of standards consistently to people who are from not only both New York and California, but every other part of the world. It would be unreasonable to try and take one culture (like SF Bay area culture, since YC is based there) and enforce it on the forum, since you can’t reasonably expect everyone to be familiar with that culture’s norms.
The way you solve it is by using a looser set of standards that a broader set of people can agree on, and letting comments slide even if they don’t match what is seen as polite in your own culture. Postel’s law and all that.
If somebody says something like “Add [2014] to title.” you can only interpret it as “not nice” if you judge it against some specific cultural norm, like a California cultural norm. That’s not a good way to judge comments on HN, because HN gets comments from all over the world—and if you, for example, judge it from a New York cultural norm, you’d say “This person is being polite by getting to the point and not wasting my time.”
The part I was objecting to is actually the flip side of Postel's Law, for what you should do. The comment I replied to claimed "nice is overrated". It's not, because people actually like it when you are nice to them. And because everyone has a differing scale of what they personally consider to be polite, it's good to overcompensate a bit to try to appeal to a wider audience. You can't please everyone, but you can definitely make very small actions without descending into "toxic niceness" or whatever and avoid most problems.
Of course, I changed the title regardless, so it's not like it "really mattered"; I wouldn't have replied to this conversation if someone didn't start this thread. I certainly don't intend to demand anything from anyone. But kindness is generally free or very cheap, so I find it to be smarter to err on the side of being nice. And that's on top of whatever moral framework or whatever you may have that guides you to be considerate, hold doors open for other people, whatever, that you do to make the world a better place.