There are many questions that can be asked for software projects, with most of these questions starting with ‘Why…?’. This is true for the challenge of proving that cascading stylesheets are Turing-complete, or that you don’t need all those fancy ISA bits of an ARM processors when you already got the LDM
and STM
commands in the 32-bit ISA. What originally started off as a bit of a running gag in a group of developers led to [Kellan Clark] implementing a Turing-complete computer and a functioning interpreter using nothing but these two opcodes.
These two opcodes essentially allow the storing or reading of data into memory from any combination of the 16 general-purpose registers (GPRs). This makes them both extremely versatile and also extremely open to ‘abuse’ like in this example. For a straightforward implementation that could prove the concept, [Kellan] decided to pick one of everyone’s favorite esoteric programming languages: Brainf**k, creating the charmingly titled Armf**k that allows anyone to write BF programs for any suitable ARM processor, like the ARM7TDMI in the Game Boy Advance that [Kellan] targeted.
As a proof of concept it’s unquestioningly intriguing, and a great example of how the most powerful parts of any ISA are those that move data around. After all, as anyone who writes ASM and C knows, computers are just machines that can copy bytes around really fast to make stuff happen. Mind-blowing examples like these serve to illustrate that point quite well.
Tip kindly provided by [eeucalyptus].
What does LDM & ST stand for? (I feel like a explanation of them is missing).
LoaD a list of Multiple (registers) from sequential source memory addresses.
e.g.
ADR R0, SOURCE_MEMORY_ADDRESS
LDM R0, {R1-R6}
STore a list of Multiple registers to sequential destination memory addresses.
e.g.
LDR R7, = (DESTINATION_MEMORY_ADDRESS)
STM R7, {R0-R15}
ref: https://keleshev.com/ldm-my-favorite-arm-instruction/
Yes, not exactly a simple instruction. How are the barrel shifter and conditional execution handled?
There is no barrel shifter since Brainfuck can only increment and decrement numbers. Conditional execution is done by using self-modifying code to jump to one of two immediate addresses (the program counter is a general purpose register on ARM).
:s/s/u
Pattern not found: s
Is there any explanation of how it works in principle?
I figure it was messing around with general purpose registers just from the title, so probably similar to same tricks known for years on Z80s.
Implementing same thing with a couple of cheap doppler modules and the vibrational modes of a coffee can is left as an exercise to the reader.
Basic addition and subtraction can be done by abusing writeback, data cells are represented as a linked list which can be traversed using only loads and stores, Brainfuck instructions must be converted into certain addresses by a script and inserted at the end of the ROM, and conditional execution is done with self-modifying code.
For more detail, check out the blog post https://github.com/KellanClark/armfuck
Oops. I posted the wrong link. https://kellanclark.github.io/2023/09/18/armfuck/
Thanks. It would have been helpful if that link was included in the article.
Back in the 1980s, a friend of my friend implemented a OIC (One Instruction Computer) at UMIST, implemented it out of logic, built a compiler for it, etc.
The single instruction was (if I remember correctly) “reverse subtract, branch if borrow”.
Kellan here. Thanks for writing this. I didn’t think anyone would care about my little meme project lol. If anyone’s interested in how this works, there’s an accompanying blog post explaining everything https://kellanclark.github.io/2023/09/18/armfuck/
I think much morefun is ONE instruction show…
https://www.youtube.com/watch?v=R7EEoWg6Ekk
taktentus use one instructions ;-)
https://esolangs.org/wiki/Taktentus