Can you learn 64-bit ARM assembly by playing TIS-100 levels?

Background

In TIS-100 you solve programming challenges using simple assembly instructions. By solving challenges you gradually unlock more levels and learn more about the story. It’s a cool game and I wanted to know what the levels would be like with real assembly.

The simplest way I could think of was to write some of the levels in C and call a function written in assembly containing the solution code. For the architecture I chose 64-bit arm (AArch64), it’s instruction set is called A64. This also means that I need to run the program’s in qemu because I don’t have an arm based laptop.

So what does it look like?
image could not be loaded
On the right you can see the assembly code, after “asm_code:” the actual instructions start. On the left you can see the output of the c program, containing the TIS-100 challenge, the input, expected output and output of the assembly function. The first column in the OUT table is the expected output.

Here is the same level in TIS-100: image could not be loaded

In TIS-100 you generally need more instructions than in A64. This has two big reasons, there are more instructions and registers available in A64. In TIS-100 you need many nodes and instructions to multiply two values, in A64 it’s a singe instruction.

A primer on A64 assembly:

There are 31 general purpose registers, X0 to X30, some have special functions but you can do whatever you want with X0 to X8.

Some simple A64 instructions:

MOV X0, X1      \\ X0 = X1;  
SUB X0, X1, X2  \\ X0 = X1 - X2;  
ADD X1, X0, X3  \\ X1 = X0 + X3;  
MUL X2, X1, X0  \\ X2 = X1 * X0;  

When you call a function written in A64 assembly the arguments are directly passed to the registers.The return value is the value contained in X0 when the RET instruction is executed. This means that X0 is set to the value of the first argument, X1 to the second argument, X2 to the third, etcetera.

To write a function that multiplies its argument by 5 we need the following instructions:

MOV X1, #5     \\\ X1 = 5;  
MUL X0, X0, X1 \\ X0 = X0 * X1;  
RET            \\ return X0;  

We also need to tell the assembler where the code starts:

    .global asm_code

asm_code:  
    MOV X1, #5  
    MUL X0, X0, X1  
    RET  

This is the minimal amount of information we need to give to the assembler.

Lets go over the code in the screenshot:

SUB X3, X1, X2   \\ X3 = X1 - X2;  
SUB X4, X2, X1   \\ X4 = X2 - X1;  
STP X3, X4, [X0] \\ X0[0] = X3; X0[1] = X4;  
RET              \\ return X0;  

STP stands for store pair, it stores the value of two registers to memory. X0 contains the pointer to an array.

Conclusion:

I solved 10 of the early TIS-100 levels using A64 and it was a lot of fun. After those levels it became apparent that the challenges don’t always translate well and I decided to write challenges specifically to learn AArch64 assembly. (More on that in the future)

Do I think TIS-100 would be a better game if it used real assembly?
No, the small instruction set and node-based architecture make it quite the unique puzzle game. Something interesting for new and seasoned programmers alike.

A benefit of using arm assembly, the levels will run just fine in termux on most modern android tablets and phones.
image could not be loaded

I have posted the levels and solutions to github, along with the commands I used to run them.

For the sake of simplicity I skipped over details, if you are interested in learning more about the 64-bit arm instruction set, the ISA introduction is a good place to start.