In this beginner's tutorial, we will write a simple non-terminating cat program in Malbolge. This tutorial is organized as follows. At first, a cat program written in the assembly language HeLL is explained in detail. To do so, the cat program is debugged using the HeLL IDE. Afterwards, we translate the cat program into Malbolge code manually, using the Malbolge pages of Lou Scheffer and a ternary number system calculator.
Hint: This tutorial is being revised at the moment. Please, don't be surprised by abruptly formatting change during the tutorial.
Please read the Malbolge language definition. It is not necessary that you have understood everything or learned it by heart. However, you may need to lookup some details of the language definition at certain times during this tutorial by your own.
There are two types of techniques to write Malbolge programs. The first technique is for writing programs that simply print out some text and terminate afterwards. As you see, capabilities of this technique are very limited. There are even some generators for this task. The second technique is suited for writing complex Malbolge programs, i.e. programs with conditional branches, loops, and input processing. If this technique is used, the programmer starts to create a Malbolge program under the assumption that every memory cell of the virtual Malbolge machine can be initialized with completely arbitrary values on startup. From the language description, you know that this assumption is not true for Malbolge. Thus, in a second step, the programmer (or an automated Malbolge assembler like LMAO) generates Malbolge code that does nothing else than writing the required values into the corresponding memory cells.
There exist two assembly languages for Malbolge: HeLL and LAL. LAL is the older one, created by some really cool Japanese researchers who were the first who ever really programmed in Malbolge. However, back in 2013, a precise specification of LAL has not been available publically (this has changed in the meantime). That was the reason for creating HeLL.
Both languages introduce labels, so that the programmer doesn't need to use ternary memory addresses all the time, and some mnemonic for pure Malbolge instructions (e.g. Jmp for i). The assembler (for HeLL, this is LMAO) cares about the memory layout and afterwards generates the Malbolge code to initialize the memory. That's almost all these assembly languages do, so they are still a lot of Malbolge.
This tutorial starts to teach you HeLL, because it's much easier to understand than pure Malbolge. The second part of the tutorial covers the manual translation of HeLL to Malbolge. If you struggle with understanding this tutorial, please don't hesitate to mail me your questions.
In Malbolge, code and data are stored together in the same memory. However, when writing Malbolge programs, it makes sense to distinguish between memory cells that are actually used for the code and those that are used for data. In HeLL, the words .CODE and .DATA are used to define the corresponding sections.
All code and all data within these sections must be assigned to a label. For the code section, it is important to be aware of the fact that Malbolge instructions are cyclic self-modifying. This kind of modification takes place after execution of an instruction and is often called encryption. In HeLL, a cycle can be specified by all of its components, separated by slashes each. E.g., Nop/MovD is a Nop instruction (no operation) that turns into a MovD instruction after encryption. After its second encryption, it is turned into a Nop instruction again, and so on.
It is not possible to build every cycle in Malbolge. If impossible cycles occur, the Malbolge assembler will throw an error. More information about valid cycles and how to find them will be given later in this tutorial. If no cycle is specified, the Malbolge assembler will choose an arbitrary cycle that start with the given instruction.
Below, you can find the entire cat program written in HeLL. The actual program logic appears in the data section.
.CODE MOVD: Nop/MovD Jmp IN_OUT: In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop Jmp .DATA ENTRY: IN_OUT ?- R_MOVD MOVD ENTRY
When a HeLL program is started, the code pointer C points to a random Jmp instruction while the data pointer D points to the data at the ENTRY label. Thus, for every HeLL program, the first data word at the ENTRY label should always be a pointer to the CODE section with the first command to be executed.
In our case, we start with the IN_OUT code snippet that reads a character from stdin into the A register and, afterwards, performs a Jmp to another code snippet specified in the DATA section. Note that the In/Nop/Out/Nop/Nop/Nop/Nop/Nop command turns into Nop/Out/Nop/Nop/Nop/Nop/Nop/In after execution.
Let us debug the above HeLL program using the HeLL IDE for a better understanding of the program steps described above as well as the further execution steps. The debugger can be started form the menu bar.
This turns the HeLL IDE into debugging mode.
The yellow arrow points at the memory position the virtual Malbolge machine's data register D is pointing to. In the code section, the current position of each instruction cycle is marked bold. At program start, this will always be the first instruction of each cycle. When a HeLL program is started, the code register C is guarenteed to point at a Jmp instruction. Typically, this Jmp instruction lies in Malbolge code somewhere outside of the HeLL code. Thus, the position of the C register cannot be marked in the debugger yet. Starting with the next step, the C register's position will be indicated by a green arrow.
We can see the state of the virtual Malbolge machine's registers and specific memory cells at the right side. While the C register points to some unknown memory address containing a Jmp/Nop/Nop/... instruction cycle, the D register points to a memory cell containing the value IN_OUT - 1. If you look at the HeLL code, you notice that the memory cell pointed at by the yellow "data" arrow should actually hold the value IN_OUT. This contradicts the value displayed at the right side of the window. However, the value on the right side is the real one. The reason is that, whenever a HeLL program is assembled to Malbolge, every reference is decremented by one automatically.
The reason for this strange behaviour is as follows. Recall how an instruction in Malbolge is executed.
In order to save the user from decrementing every jump address again and again, the LMAO Malbolge assembler performs the decrement by one automatically. Since the D register behaves analogously during a MovD instruction, unexceptional every reference is decremented by one by the Malbolge assembler automatically.
Let us continue to discuss the debug view of the HeLL IDE shown above. The value of the A register is not defined when the execution of a HeLL program starts. Thus, we should never read from the A register until we have written a value into it using the Rot or In instruction, but not the Opr instruction. In fact, the A register is initialized with the value ENTRY - 1 if the current version of LMAO is used. This behavior may change for future versions of LMAO, so we must not rely on it.
Additionally to the registers and memory cells described above, it is possible to watch user-defined expressions with the HeLL IDE. To demonstrate this feature, we watch the content of the memory cells at MOVD and IN_OUT. While MOVD is the address of the Nop/MovD instruction, [MOVD] is the content at that address, i.e. the Malbolge instruction Nop/MovD itself.
The value of [MOVD] and [IN_OUT] are the encoded Malbolge instructions Nop/MovD and In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop. In this case, the instruction Nop/MovD is encoded by the ASCII character 'F'. Recall that in Malbolge, the encoding of an instruction depends on its position in memory modulo 94. Later, we will observe that the values at MOVD and IN_OUT will change whenever the current position in the corresponding instruction cycle is changed.
Let us execute one single step of the program now. Recall that the C register points to a Jmp instruction while the D register points to a memory cell containing the value IN_OUT - 1. Now we can observe how these values have changed.
Now, the code register C is pointing at the In instruction. The D register has been incremented and is pointing now to a memory cell with undefined content, which is indicated by ?-.
During the next step, the program will execute the In instruction and thus read one byte of user input. So, before running this step, let us type something for the program to read into the terminal at the bottom of the HeLL IDE.
We have just written the word "foo". Now we can execute the next step that will load the character 'f' of our input into the A register.
As expected, the A register holds the value 'f' (ternary 0t10210) now. Also, the instruction In has been modified, so that the cycle's position is now at the first Nop instruction (it is highlighted by boldface in the screenshot above). The C and D registers have been incremented.
The next command executed will be a Jmp to R_MOVD. The R_ prefix is a special HeLL notation, which is a mnemonic for restore. Literally, this command will restore the MovD instruction at the label MOVD. At the moment, the instruction is in a "destroyed" state, because the active instruction of its cycle is the Nop command, but not the MovD command.
Internally, R_ prefix is simply the same as an addition by one. Thus, R_MOVD is just an alternative notation for MOVD+1. Recall that LMAO always decrements references by one. So, the memory cell initialized with R_MOVD will finally hold the reference to MOVD without decrementation. Let's figure out why this restores the instruction.
As you can see above, once the Jmp to MOVD has been executed, the code register C points to another Jmp instruction: the Jmp instruction behind the MOVD label. The active instruction of the preceeding instruction cycle has changed from Nop to MovD, as indicated by boldface. Why did this happen? Recall that, whenever the Malbolge interpreter has executed an instruction, the instruction that is pointed to by the C register is modified. This happens before the C and D registers are incremented. For the Jmp instruction, the timing is as follows. At first, the C register is set to the new position. Then the command at the new C register's position is modified. Finally, the C and D registers are incremented.
Restoring a 2-cycle instruction by using the Jmp command in this way is an essential technique when writing HeLL or, respectively, Malbolge programs. This is the reason why the R_ prefix is implemented in HeLL and called "restore".
Okay, let us execute the next step, a Jmp to MOVD.
Now, the code register C points at the MovD instruction that has been restored just before and the data register D points at the value ENTRY - 1. Because both registers will be incremented after execution of the instruction, the D register will contain the address of the ENTRY label after the next step.
There you go! The virtual Malbolge machine is nearly in its initial state again. The code register points at a Jmp instruction and the Nop/MovD cycle at MOVD is in the Nop state again. And, last but not least, the data register points to the entry point ENTRY.
However, there are two important differences:
You can imagine how things will go on. There will be one pass through the program in which nothing will happen when the command at IN_OUT is executed. In the subsequent pass, the character 'f' stored in the A register will be printed out. After a few more passes, the next character will be read. And so on ad nauseam.
We will manually translate the HeLL cat program into Malbolge now. This makes sense for simple programs, because this way the code will become more succinct than the code generated by LMAO. And, even more important, this is a great opportunity to increase your knowledge about Malbolge and how LMAO works.
At first, let us do a little modification on the cat program: we change the Nop/MovD cycle at MOVD to MovD/Nop and adjust the program flow in the .DATA section accordingly.
.CODE MOVD: MovD/Nop Jmp IN_OUT: In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop Jmp .DATA loop: R_MOVD ENTRY: IN_OUT ?- MOVD loop
This modification makes the construction of Malbolge code easier. This is due to the fact that in Malbolge, memory cells can be initialized with a few values directly. If we want a memory cell to hold a different value, we must construct it during runtime. This is, beside choosing memory positions, the main task LMAO does for us. And it is very annoying, so we want to keep it at a minimum when generating Malbolge code manually.
You may ask: why is it possible to initialize a cell with the cycle MovD/Nop, but not with the cycle Nop/MovD. Well, to explain that, we have to dive into Malbolge cycles and two different types of Nop commands.
Now we are ready to translate the HeLL program to Malbolge manually. At first, we need to do the positioning of memory cells. For code blocks, only specific offsets are allowed, depending on the instruction cycles. We will see the details of this later. We can use the @ operator in HeLL to force memory positions.
.CODE @0t20000101 MOVD: MovD/Nop Jmp @0t20020111 IN_OUT: In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop Jmp .DATA @0t20000000 loop: 0t20000101 ENTRY: 0t20020110 ?- 0t20000100 0t12222222
It is still possible to build the program with LMAO and run it in the HeLL IDE.
The program uses large address values. They must be generated in the .DATA section, but doing so manually is inconvenient. It is easier to generate small numbers at runtime than big ones. Thus, let us change the memory addresses to the ASCII range. Since memory cells can be initialized with a few ASCII characters before runtime, we may even be able to write the correct value to some memory cells of the .DATA section directly if we were lucky.
Unfortunately, this program cannot be assembled with LMAO any longer. The reason is that LMAO is not able to initialize memory cells at the beginning of the address space.
The address of the data section is chosen a bit arbitrary. The data section must not intersect
with the code section.
Note that it must not intersect with a memory cell directly preceding a code block,
because this memory cell will be modified whenever the program jumps into the code block
(this behavior has already been explained). If the data section intersects with
such a memory cell, it will cause undefined behavior or even crash the Malbolge reference
interpreter.
The choice of the addresses of the two code blocks are more interesting than the address of the data block.
We have to ensure that the given instruction cycles exist at the corresponding addresses.
We can look up this at Lou Scheffer's website Instruction Cycles in Malbolge.
We can see that there exists a Nop/In/Nop/Out/Nop/Nop/Nop/Nop/Nop cycle at address 37.
Note that the dots indicate invalid instructions that are interpreted as Nops while Nop indicates a valid instruction. Thus, only the non-dot instructions can be initialized directly. If we put an In or an Out instruction at address 37, it matches the desired instruction cycle. We want to start the cycle with an In instruction.
A MovD/Nop cycle exists at address 60 and address 64. Let us choose address 60 randomly.
This leads to the memory layout you have already seen above.
Now it's time to write real Malbolge code. Therefore, we use a further page of Lou Scheffer: Valid Instructions
At first, we place an In instruction at address 37 to get the In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop instruction cycle.
To put the In instruction there – a slash in normalized Malbolge – we have to write a P at that address. For the MovD instruction, a j in normalized Malbolge, we write a J at address 60.
Let us place the Jmp instructions (normalized Malbolge: i) right behind these two instructions.
Now our Malbolge program is as following.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
We have constructed the entire code section already. It remains to initialize the data section. We continue to use Lou Scheffer's valid instructions overview, to identify the memory cells we can directly initialize in the Malbolge code.
We are lucky: The 60 at address 39 and the 38 at address 43 can be initialized directly.
The other two values of the data section – the 36 at address 40 and the 59 at address 42 – must not be written into the Malbolge code directly. We will solve this problem in the following.
Before we do so, let us take a look at our current Malbolge program.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
43 | & | v | 38 (loop) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
We have to initialize the remaining memory cells of the data section now.
Since this cannot be done directly, the memory cells must be initialized during runtime.
Thus, we have to write Malbolge code that initializes the remaining memory cells.
For this task, use-once code is sufficient, i.e. we need not care about instruction cycles.
We need the ternary number system and the Opr instruction now, because data manipulation
in Malbolge is only possible in ternary and by the two instruction Opr and Rot.
For the ternary number system, you may want to use an
online calculator.
You may also want to look up the Opr instruction in
Esolang's Malbolge documentation if you don't know it by heart yet.
Before we write Malbolge code for runtime initialization, we should do some very basic initialization. Every Malbolge program begins with its three registers set to zero. We have to separate the code and data pointer before any Rot or Opr instruction is performed. Otherwise it is very likely that the reference interpreter will crash. We start our Malbolge program with a MovD instruction to separate both pointers (alternatively, a Jmp instruction would also work). A MovD instruction at address 0 is coded by an opening bracket, which has the ASCII value 40. Thus, in the next step, the D register will have the value 41. Our Malbolge program looks as follows now:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
43 | & | v | 38 (loop) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
As stated above, the A register is initialized with zero. This value, resp. 0t1111111111, is very useful, so let us save the A register before we do anything that changes its value. We can safe the register by performing an Opr instruction on a memory cell that does not contain any ternary 2. We can directly use the current address of the D register, address 41, which is perfect for this purpose: It is not used by our HeLL code and it can be initialized with 0t0000001111, which does not contain any ternary 2. Now our Malbolge program looks as follows:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
41 | ( | v | 0t0000001111; will be 0t1111111111 after the 2nd step |
43 | & | v | 38 (loop) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
The first memory cell we will initialize is cell 40. We have to generate the value 36 there – 1100 in ternary.
From Lou Scheffer's valid instructions overview, we know that address 40 can be initialized with 58 – ternary 2011. This value is close to 0t0000001100, because it can be transformed into the desired value by a single Opr with A=0t1111112011.
So, let us write code that sets the A register to 0t1111112011. This can be done by reading 0t0000000200 into the A register and doing a Opr in 0t0000002000. Both are easy tasks since 0t0000002000 is a valid ASCII code.
Our Malbolge code to initialize address 40 is as follows:
Rot 0t0000002000 // afterwards A = 0t0000000200 Opr A into 0t0000002000 // afterwards A = 0t1111112011 Opr A into 0t0000002011 at address 40 // afterwards [40] = 0t0000001100
The D register points at address 42 right now. However, we should not use address 42, because we need to initialize it later and we may want to write a value there that helps us for its initialization (like the value 0t0000002011 that we put at address 40.
However, we are lucky and can initialize address 44 and address 45 with 0t0000002000, which is 54 in the decimal number system).
We can use the Nop instruction to set the D register to 44, because it is incremented after every instruction.
After two Nops, the D register points to address 44, so that we can perform the Rot instruction followed by an Opr instruction on address 45.
Our current Malbolge program:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, should become 0t0000001100 later |
41 | ( | v | 0t0000001111; will be 0t1111111111 after the 2nd step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000200 after the 5th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
The A register contains the value 0t1111112011 now and the D register points to address 46. To complete initialization of address 40, the D register must be moved there for an Opr instruction. Therefore, we write the value 35 at address 46 and move the D register to address 36 by MovD. Afterwards, we add four Nop instructions to reach address 40, where we perform the Opr instruction.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t1111111111 after the 2nd step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000200 after the 5th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
It remains to initialize memory cell 42 with the value 59 – 2012 in ternary. It is possible to initialize cell 42 with 0t0000002002, which can be changed to the target value by an Opr with A = 0t1111111101. 0t1111111101 can be generated by performing an Opr instruction with A = 0t0000000020 and [D] = 0t0000000000. We have written the value 0t1111111111 at address 41 before, which can be changed to 0t0000000000 by an Opr with itself.
Rot 0t1111111111 at address 41 // afterwards A = 0t1111111111 Opr A into 0t1111111111 at address 41 // afterwards [41] = 0t0000000000 Rot 0t0000000200 at address 44 // afterwards A = 0t0000000020 Opr A into 0t0000000000 at address 41 // afterwards A = 0t1111111101 Opr A into 0t0000002002 at address 42 // afterwards [42] = 0t0000002012
We start with setting the value at address 41 to 0t0000000000. At the moment, the D register points to address 41, because the last thing we did was initializing address 40. We can use the value 38 stored at address 43 to move the D register back to address 41 again and write the following code.
// D = 41, [D] = A = 0t1111111111 Rot Nop // D = 43, [D] = 38 MovD // D = 39 Nop Nop // D = 41, [D] = 0t1111111111 Opr
Our Malbolge code is now at follows.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
12 | y | * | Rot |
13 | 7 | o | Nop |
14 | x | j | MovD |
15 | 5 | o | Nop |
16 | 4 | o | Nop |
17 | - | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t0000000000 after the 18th step |
42 | 8 | i | 0t0000002002, should become 0t0000002012 later |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000200 after the 5th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
The D register points to address 42 now. We can rotate 0t0000000200 at address 44 to load 0t0000000020 into the A register, Opr it into address 41 and Opr the result into address 42.
// D = 42 Nop Nop // D = 44, [D] = 0t0000000200 Rot // A = 0t0000000020 Nop // D = 46, [D] = 35 MovD // D = 36 Nop Nop Nop Nop Nop // D = 41, [D] = 0t0000000000 Opr // D = 42, A = 0t1111111101, [D] = 0t0000002002 Opr // [42] = 0t0000002010
Our current Malbolge program:
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
12 | y | * | Rot |
13 | 7 | o | Nop |
14 | x | j | MovD |
15 | 5 | o | Nop |
16 | 4 | o | Nop |
17 | - | p | Opr |
18 | 2 | o | Nop |
19 | 1 | o | Nop |
20 | q | * | Rot |
21 | / | o | Nop |
22 | p | j | MovD |
23 | - | o | Nop |
24 | , | o | Nop |
25 | + | o | Nop |
26 | * | o | Nop |
27 | ) | o | Nop |
28 | " | p | Opr |
29 | ! | p | Opr |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t1111111101 after the 29th step |
42 | 8 | i | 0t0000002002, will be 0t0000002012 after the 30th step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000020 after the 21th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
It remains to start the completely initialized HeLL program. Therefore we only need to perform a Jmp instruction while the D register points to the entry point (labeled with ENTRY in HeLL), which is at address 40. At the moment, the D register points to 43. After a MovD instruction, it will point to address 39. Thus, we add the following code:
MovD Nop Jmp
The final Malbolge program looks as follows.
Address | Malbolge code | Normalized | Comment |
---|---|---|---|
0 | ( | j | MovD |
1 | = | p | Opr |
2 | B | o | Nop |
3 | A | o | Nop |
4 | # | * | Rot |
5 | 9 | p | Opr |
6 | " | j | MovD |
7 | = | o | Nop |
8 | < | o | Nop |
9 | ; | o | Nop |
10 | : | o | Nop |
11 | 3 | p | Opr |
12 | y | * | Rot |
13 | 7 | o | Nop |
14 | x | j | MovD |
15 | 5 | o | Nop |
16 | 4 | o | Nop |
17 | - | p | Opr |
18 | 2 | o | Nop |
19 | 1 | o | Nop |
20 | q | * | Rot |
21 | / | o | Nop |
22 | p | j | MovD |
23 | - | o | Nop |
24 | , | o | Nop |
25 | + | o | Nop |
26 | * | o | Nop |
27 | ) | o | Nop |
28 | " | p | Opr |
29 | ! | p | Opr |
30 | h | j | MovD |
31 | % | o | Nop |
32 | B | i | Jmp |
37 | P | / | In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop |
38 | < | i | Jmp |
39 | < | < | 60 (R_MOVD) |
40 | : | i | 0t0000002011, will be 0t0000001100 after the 12th step |
41 | ( | v | 0t0000001111; will be 0t1111111101 after the 29th step |
42 | 8 | i | 0t0000002002, will be 0t0000002012 after the 30th step |
43 | & | v | 38 (loop) |
44 | 6 | i | 0t000002000; will be 0t0000000020 after the 21th step |
45 | 6 | < | 0t000002000; will be 0t1111112011 after the 6th step |
46 | # | v | 35 (destination for D register) |
60 | J | j | MovD/Nop |
61 | % | i | Jmp |
We are lucky that the entire initialization code fits into the memory before address 37.
The only thing that is left to obtain our final Malbolge program is to fill the unused memory cells – addresses 33 to 36 and addresses 47 59 – with arbitrary valid instructions. We randomly choose the Hlt instruction for this purpose and obtain the final Malbolge cat program:
(=BA#9"=<;:3y7x54-21q/p-,+*)"!h%B0/. ~P< <:(8& 66#"!~}|{zyxwvu gJ%
Note that this program does not terminate.