Malbolge Tutorial – Learning Malbolge

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.

Table of Contents

  1. Introduction
  2. A cat Program in HeLL
  3. Assembling the cat Program to Malbolge
  4. Further Reading
  5. List of Tools

1. Introduction

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.

2. A cat Program in HeLL

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.

The debug mode of the HeLL IDE can be started via the menu.

This turns the HeLL IDE into debugging mode.

HeLL IDE: A yellow arrow point at 'IN_OUT' below the 'ENTRY' label.
The instruction 'Nop' of the cycle 'Nop/MovD' is marked bold.
The instruction 'In' of the cycle 'In/Nop/Out/Nop/Nop/Nop/Nop/Nop' is marked
as well. Also the Jmp instructions are bold. At the bottom one can see
a terminal. At the right the following information are provided:
C: somewhere in the initialization section;
[C]: Jmp/Nop/Nop/Nop/...;
[D]: IN_OUT - 1; A: 0t2222211212 '9'.
There is an empty table below his text, the two columns of which
are labeled with address and value.

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.

  1. The instruction at the C register is executed.
  2. The instruction at the C register is encrypted.
  3. Both, the registers C and D are incremented by one.
Thus, if a Jmp instruction is executed while the D register points to a memory cell containing the value IN_OUT - 1, the C register is set to IN_OUT - 1 during the first step of the instruction execution, but incremented by one directly afterwards, resulting in the C register containing the value IN_OUT. Note that the encryption is performed between these two steps. This results in encryption of the instruction at address IN_OUT - 1. The Jmp command is not encrypted, but the command before the destination defined in the HeLL code.

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.

HeLL IDE: In the right table, two rows were added.
The first row contains address [MOVD] and value 0t0000002121 'F',
while the second row contains address [IN_OUT] and value 0t0000002222 'P'.
The remaining parts of the windows are unchanged.

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.

HeLL IDE: A green arrow appeared,
pointing at the instruction cycle In/Nop/Out/Nop/Nop/Nop/Nop/Nop.
The yellow arrow points at ?- now, directly behind IN_OUT.
The text above the table at the right side is now:
C: IN_OUT;
[C]: In/Nop/Out/Nop/Nop/Nop/Nop/Nop;
[D]: ?-; A: 0t2222211212 '9'.
The remaining parts of the window are not 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.

HeLL IDE: The word 'foo' has been entered at the terminal.
Nothing else has been changed.

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.

HeLL IDE: The first Nop of the In/Nop/Out/Nop/Nop/Nop/Nop/Nop
instruction cycle is now marked. The green arrow points to
the Jmp instruction right below In/Nop/Out/Nop/Nop/Nop/Nop/Nop.
The yellow arrow points at R_MOVD below ?-. Information at the right side
of the window:
C: IN_OUT + 1;
[C]: Jmp/Nop/Nop/Nop/MovD;
[D]: R_MOVD - 1;
A: 0t0000010210 'f'.
The value of [IN_OUT] is now 0t0000002110 'B'.
The other things are unchanged.

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.

HeLL IDE: The MovD of the Nop/MovD instruction cycle is now marked.
The green arrow points at the Jmp instruction below Nop/MovD.
The yellow arrow points at MOVD below R_MOVD now. Information at the right side:
C: R_MOVD;
[C]: Jmp/Nop/Nop/Nop/Nop/Nop;
[D]: MOVD - 1;
A: 0t0000010210 'f'.
The value of [MOVD] changed to 0t0000002202 'J'.
The remaining parts of the window are unchanged.

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.

HeLL IDE: The green arrow points at the Nop/MovD cycle.
The yellow arrow points at ENTRY right behind MOVD.
Information at the right side:
C: MOVD;
[C]: MovD/Nop;
[D]: ENTRY - 1;
A: 0t0000010210 'f'.
Nothing else has been changed.

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.

HeLL IDE: The state of the cat program is almost
equivalent to the beginning.
However, the In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop instruction cycle
is now in the first Nop, which is indicated by bold font
and the value of [IN_OUT] at the right side of the window.
The green arrow points to the Jmp instruction below the Nop/MovD cycle
and the C register has the value MOVD + 1, while [C] has the value
Jmp/Nop/Nop/Nop/Nop/Nop.

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:

  • The A register still holds the ASCII value of 'f'.
  • The cycle at IN_OUT, which started as In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop, is now in the configuration Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop/In.

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.

3. Assembling the cat Program to Malbolge

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.

Choosing memory positions

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.

HeLL IDE: The modified cat program can be built with LMAO
and executed as well.

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.

.CODE @60 MOVD: MovD/Nop Jmp [empty line] @37 IN_OUT: In/Nop/Out/Nop/Nop/Nop/Nop/Nop Jmp .DATA @39 loop: 60 ENTRY: 36 ?- 59 38

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.

offset 37:.INPUT .OUTPUT ..... offset 60:LOAD D . offset 64:.LOAD D
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.

Direct initialization of memory cells

Now it's time to write real Malbolge code. Therefore, we use a further page of Lou Scheffer: Valid Instructions

37: 80(P)->/(47) 38: 60(<)->i(105) 60: 74(J)->j(106) 61: 37(%)->i(105)
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.

AddressMalbolge codeNormalizedComment
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
60JjMovD/Nop
61%iJmp

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.

39: 60(<)-><(60) 43: 38(&)->v(118)
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.

AddressMalbolge codeNormalizedComment
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
43&v38 (loop)
60JjMovD/Nop
61%iJmp

Runtime initialization of memory cells

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 runtime initialization begins

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:

AddressMalbolge codeNormalizedComment
0(jMovD
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
43&v38 (loop)
60JjMovD/Nop
61%iJmp

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:

AddressMalbolge codeNormalizedComment
0(jMovD
1=pOpr
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
41(v0t0000001111; will be 0t1111111111 after the 2nd step
43&v38 (loop)
60JjMovD/Nop
61%iJmp

Initialization of address 40

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.
Malbolge tools: Opr(0t1111112011, 0t0000002011) = 0t0000001100
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:

AddressMalbolge codeNormalizedComment
0(jMovD
1=pOpr
2BoNop
3AoNop
4#*Rot
59pOpr
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
40:i0t0000002011, should become 0t0000001100 later
41(v0t0000001111; will be 0t1111111111 after the 2nd step
43&v38 (loop)
446i0t000002000; will be 0t0000000200 after the 5th step
456<0t000002000; will be 0t1111112011 after the 6th step
60JjMovD/Nop
61%iJmp

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.

AddressMalbolge codeNormalizedComment
0(jMovD
1=pOpr
2BoNop
3AoNop
4#*Rot
59pOpr
6"jMovD
7=oNop
8<oNop
9;oNop
10:oNop
113pOpr
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
40:i0t0000002011, will be 0t0000001100 after the 12th step
41(v0t0000001111; will be 0t1111111111 after the 2nd step
43&v38 (loop)
446i0t000002000; will be 0t0000000200 after the 5th step
456<0t000002000; will be 0t1111112011 after the 6th step
46#v35 (destination for D register)
60JjMovD/Nop
61%iJmp

Initialization of address 42

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.

AddressMalbolge codeNormalizedComment
0(jMovD
1=pOpr
2BoNop
3AoNop
4#*Rot
59pOpr
6"jMovD
7=oNop
8<oNop
9;oNop
10:oNop
113pOpr
12y*Rot
137oNop
14xjMovD
155oNop
164oNop
17-pOpr
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
40:i0t0000002011, will be 0t0000001100 after the 12th step
41(v0t0000001111; will be 0t0000000000 after the 18th step
428i0t0000002002, should become 0t0000002012 later
43&v38 (loop)
446i0t000002000; will be 0t0000000200 after the 5th step
456<0t000002000; will be 0t1111112011 after the 6th step
46#v35 (destination for D register)
60JjMovD/Nop
61%iJmp

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:

AddressMalbolge codeNormalizedComment
0(jMovD
1=pOpr
2BoNop
3AoNop
4#*Rot
59pOpr
6"jMovD
7=oNop
8<oNop
9;oNop
10:oNop
113pOpr
12y*Rot
137oNop
14xjMovD
155oNop
164oNop
17-pOpr
182oNop
191oNop
20q*Rot
21/oNop
22pjMovD
23-oNop
24,oNop
25+oNop
26*oNop
27)oNop
28"pOpr
29!pOpr
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
40:i0t0000002011, will be 0t0000001100 after the 12th step
41(v0t0000001111; will be 0t1111111101 after the 29th step
428i0t0000002002, will be 0t0000002012 after the 30th step
43&v38 (loop)
446i0t000002000; will be 0t0000000020 after the 21th step
456<0t000002000; will be 0t1111112011 after the 6th step
46#v35 (destination for D register)
60JjMovD/Nop
61%iJmp

Running the initialized code

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.

AddressMalbolge codeNormalizedComment
0(jMovD
1=pOpr
2BoNop
3AoNop
4#*Rot
59pOpr
6"jMovD
7=oNop
8<oNop
9;oNop
10:oNop
113pOpr
12y*Rot
137oNop
14xjMovD
155oNop
164oNop
17-pOpr
182oNop
191oNop
20q*Rot
21/oNop
22pjMovD
23-oNop
24,oNop
25+oNop
26*oNop
27)oNop
28"pOpr
29!pOpr
30hjMovD
31%oNop
32BiJmp
37P/In/Nop/Out/Nop/Nop/Nop/Nop/Nop/Nop
38<iJmp
39<<60 (R_MOVD)
40:i0t0000002011, will be 0t0000001100 after the 12th step
41(v0t0000001111; will be 0t1111111101 after the 29th step
428i0t0000002002, will be 0t0000002012 after the 30th step
43&v38 (loop)
446i0t000002000; will be 0t0000000020 after the 21th step
456<0t000002000; will be 0t1111112011 after the 6th step
46#v35 (destination for D register)
60JjMovD/Nop
61%iJmp

We are lucky that the entire initialization code fits into the memory before address 37.

Finishing our work

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.

4. Further Reading

A. List of Tools

Contact | Site notice (Impressum)