This game further cemented my belief that I do not want to use assembler for any serious project.
- Alpatron, reviewing Human Resource Machine
In this post, I will demonstrate how to use Compiler Explorer as a high-level IDE for creating solution designs for the assembly-level programming game Human Resource machine.
What’s going on here?
Compiler Explorer is an easy to use tool for viewing the assembly output for C/C++ code. It has many convenient features over generating assembly yourself, such as color highlighting of matching sections, filtering out noisy boilerplate code, and instant dropdown selection of different target architectures.
I was inspired to use Compiler Explorer after watching Jason Turner make frequent use of it in his C++ Weekly episodes. However, it was a bit dumbfounding to come up with a motivating use case for generating examples.
Then I remembered picking up the game Human Resource Machine, which uses the setting of computers-as-people office drudgery as a way to stealthily introduce assembly language concepts.
For the early levels, the challenges are easy to tackle and the actions of the little office-drone/accumulator-register are easy to understand. This simplicity of the actions later becomes a hinderance, as the challenges become more unmanageable due to your limited expressive options. Eventually, the complexity of the algorithms requested leads to jump diagrams that literally look like interlaced spaghetti.
The idea suddenly struck me to use GCC assembly output as a way to solve the problems of Human Resource Machine in a higher level language. This effort would provide concrete exercises to play with in Compiler Explorer, while also simplifying the task of organizing confusing jump spaghetti in the game.
Proof of concept
To evaluate the feasibility of this project, I went straight for a problem that I abandoned earlier, due to the confusion with the jump instructions. Level 17, Exclusive Lounge, asks that for two numbers coming from the inbox:
- Send a 0 to outbox if they have the same sign.
- Or send a 1 to the outbox if their signs are different.
Not an intellectually stimulating problem, but manually directing the jumps was quite the confusing chore! In my defense, I think the level designer acknowledges the complexity of the task, as the level sits on an optional challenge branch.
After loading Compiler Explorer, I came up with some quick C code for the solution:
… which resulted in the following assembly output:
The first wrinkle was with the jns instruction. Originally I was attempting to target the “jump if negative” instruction in HRM, but the opposite operation was generated from comparisons driven by ‘<’. Changing the occurrences of < to >= led to the output of js as desired.
I also noticed that I could save a copyfrom instruction if my first action involved the last argument pushed to the stack. That meant starting the first comparison with argument y instead of x as above.
To aid in translation, I marked the assembly code with references to HRM instructions, and the HRM instructions with references to the generated x86 labels:
Thoughts on the process
Suffice to say, the ‘;’ comment system in assembly was so much more convenient than the initially-cute but impatience-inducing mouse-written tape notes in HRM.
Running the program didn’t satisfy the efficiency targets for instructions or cycles, but it was incredibly gratifying to easily solve a problem previously stymied by jump spaghetti.
One disappointment was that I could not use the -O settings to automatically generate optimized code, as the tricky use of esoteric x86 instructions made the optimized output less ready for translation. If any efficiency gains were to be had, they would need to be discovered by human intuition.
Building more abstractions with C macros
Another benefit from editing C in Compiler Explorer was to enable the use of very crude subroutines, defined by C macros. Using function calls in the C code would not work well in HRM, as the overhead of pushing/popping arguments on the floor as a crude stack would be a hinderance to translate.
For example, HRM does not have an operation for comparing two variables directly, as jump comparisons are only against 0 and the value in hand. In Level 14, Maximization Room, you can use subtraction as a workaround for comparing two values a and b, returning the greater of the two:
With C macros, you can have the illusion of a function call, without the overhead of pushing to an imaginary stack:
… with x86 code that neatly organizes the resulting order of jump instructions:
Tracking pre-populated floor tiles
Sometimes a level will provide constants that you can use from the floor:
You can define local variables to generate register offsets that you can track throughout the assembly program:
Multi-line macros with multiple inputs/outputs
With help of the local variable annotation trick above, you can define macros that manipulate multiple inputs and outputs:
Careful: note that the first 2 args are also mutated if you kept their variable references, so beware of side effects on all your macro inputs.
For example, I used this trick to save the division and modulus results from a macro subroutine when solving Level 39, Re-Coordinator.
Abstractions as a design tool
While I wouldn’t joke that these sort of shenanigans are preparing me to understand the immensity of x86, this exercise really honed in the fact that abstractions matter, and that the right tooling can make mentally challenging tasks much more tractable!