This document describes the more advanced features of Mindcode.
Mindcode allows you to alter some compiler options in the source code using special #set
commands. The basic syntax is:
#set option = value;
Some of these options can be alternatively specified as parameters of the command line compiler.
Supported compiler options are described below.
Activates/deactivates automatic flushing of print output. Possible values are:
false
: Mindcode doesn't flush print output automatically.true
(the default value): When the program contains at least oneprint
orprintchar
instruction, and noprintflush
ordraw print
instructions, Mindcode adds aprintflush message1
instruction at the end of the main program body, and generates a warning.
This feature is meant for small, test scripts, where a call to printflush()
is easily missed. This situation requires new compilation and code injection into mlof processor when detected.
This option activates/deactivates runtime checks and specifies which mechanism to use when runtime checks are active. Runtime checks are a debug feature and should be only active when developing/debugging your scripts, as they make the code larger and slower.
Currently, runtime checks are generated for these operations:
- Accessing an element of an internal array by index: the check makes sure the index lies within bounds.
Possible values for the boundary-checks
directive are:
none
(the default value): no runtime checks are generated.assert
: runtime checks are generated using instructions provided by the MlogAssertions mod. The mod is available for Mindustry 7. If the mod is not installed, no runtime checks are performed, but otherwise the code runs as expected. Each runtime check takes 1 instruction. When the runtime check fails, the mod displays an error message over the processor for easier detection.minimal
: when the runtime check fails, the program execution stops on ajump
instruction (this instruction permanently jumps to itself, which can be determined by inspecting the@counter
variable in the Vars screen). Each runtime check takes 2 instructions.simple
: when the runtime check fails, the program execution stops on astop
instruction (again, this can be determined by inspecting the@counter
variable). Each runtime check takes 3 instructions.described
: when the runtime check fails, the program execution stops on astop
instruction. However, aprint
instruction containing an error message is generated just before thestop
instruction; after locating the faultingstop
instruction, the error message can be read. Each runtime check takes 4 instructions.
Use the goal
option to specify whether Mindcode should prefer to generate smaller code, or faster code. Possible values are:
size
: Mindcode tries to generate smaller code.speed
: Mindcode can generate additional instructions, if it makes the resulting code faster while adhering to the 1000 instructions limit. When several possible optimizations of this kind are available, the ones having the best effect (highest speedup per additional instruction generated) are selected until the instruction limit is reached.auto
(the default value): At this moment the setting is identical tospeed
.
This option allows to change the instruction limit used by speed optimization. The speed optimization strives not to exceed this instruction limit. In some cases, the optimization cost estimates are too conservative - some optimizations applied together may lead to code reductions that are not known to individual optimizers considering each optimization in isolation. In these cases, increasing the instruction limit might allow more optimizations to be performed. When the resulting code exceeds 1000 instructions, it is not usable in Mindustry processors and the option should be decreased or set back to 1000. (A new feature, which would perform this trial-and-error optimization automatically, is planned.)
The limit only affects the optimization for speed. The option has no effect on code generated by the compiler or optimizers which do not work in the speed optimization mode, and doesn't help reduce the code size generated outside the optimization for speed mechanism.
It is also possible to decrease the instruction limit, if you wish so. The valid range for this compiler option is 1 to 100,000 for the command-line tool, and 1 to 1,500 for the web application.
Important
Setting the limit to a very high value can have severe impact on the performance of the compiler. High values of the instruction limit might cause the code compilation to take minutes or even hours to complete.
Use the link-guards
option to specify whether Mindcode should generate guard code for linked variables. Possible values are:
false
: Mindcode doesn't generate guard code.true
(the default value): Mindcode generates guard code for all linked variables except.
For more details on guard code and rules for generating it, see Guard code for linked variables.
This option has been added to support future enhancements of Mindcode. Setting the option doesn't have any effect at this moment.
Use the optimization
option to set the optimization level of the compiler:
#set optimization = basic;
Possible values for this option are:
none
: deactivates all optimizations.basic
: performs most optimizations, except those that depend on certain assumptions about the program or Mindustry Logic.advanced
: performs additional optimizations based upon some assumptions about Mindustry Logic (e.g. that numerical id produced by a lookup instruction for Mindustry content elements is stable) or the source code (e.g. that it doesn't depend on expressions converting null values to non-null ones).experimental
: perform optimizations that are currently in the experimental phase.
The default optimization level is advanced
.
Use the passes
option to set the maximum number of optimization passes to be done:
#set passes = 10;
The default value is 3 for the web application and 25 for the command line tool. The number of optimization passes can be limited to a value between 1 and 1000 (inclusive).
A more complex code can usually benefit from more optimization passes. On the other hand, each optimization pass can take some time to complete. Limiting the total number can prevent optimization from taking too much time or consuming too many resources (this is a consideration for the web application).
This option controls the way remarks, generated through the remark() function, are propagated to the compiled code. Remarks are written into the compiled code as print
instructions. Possible values of the remarks
option are:
none
: remarks are suppressed in the compiled code - they do not appear there at all.passive
: remarks are included in the compiled code, but a jump is generated in front each block of continuous remarks, so that the print statement themselves aren't executed. This is the default value.active
: remarks are included in the compiled code and are executed, producing actual output to the text buffer.
Passive remarks can be used for putting instructions or comments in the compiled code, or to mark a specific portion of the code. Remarks in a loop may help identifying individual iterations when the loop is unrolled, for example.
Active remarks can be used to easily add debugging output to a program that can be deactivated using a compiler option (potentially through a command line switch without modifying the source code).
The Vars screen of the Mindustry processor shows all variables and their values, but the variables are displayed in the order in which they were created. This typically results in a very chaotic order of variables, where variables defined by the user are mixed with temporary variables, making it quite difficult to find a specific variable in sufficiently large programs.
This option can be used to make variables be displayed in a Mindustry processor in a well-defined order. Mindcode compiler ensures that by prepending a special block at the beginning of the program which creates user-defined variables in a specific order without altering their value. (The packcolor
instruction is used, which can read - and therefore create - up to four variables per instruction. The result is not stored anywhere so that the variable-ordering code block doesn't change values of any variables, and therefore the behavior of the program remains unaltered, except for possible difference in timing.)
The value assigned to the sort-variables directive is a list of variable categories:
linked
: variables representing linked blocks,params
: variables representing program parameters,globals
: global variables,main
: main variables,locals
: local variables,all
: user variables that aren't matched by any other specified category,none
: no variables at all. Can be used as a#set sort-variables=none;
, ensuring that no variable ordering will be performed.
It is possible to use the directive without specifying any value at all (#set sort-variables;
). In this case, the categories will be processed in the order above.
When processing the directive, the categories are processed in the given order, with all variables in a category sorted alphabetically. This defines the resulting order of variables.
Note on the linked
category: when a block is linked into the processor, a variable of that name is removed from the variable list. By putting the linked
variables first, it is very easy to see which linked blocks used by the program are not linked under their proper names.
The number of variables being sorted is limited by the instruction limit. Should the resulting program size exceeds the instruction limit, some or all variables will remain unordered.
Chooses the syntax mode to be used for compilation. Possible values are:
relaxed
(the default value): useful for shorter scripts, as it requires less boilerplate code.strict
: useful for larger projects, as it enforces additional rules designed to make source code more maintainable.mixed
: designed to help with a transition of relaxed syntax code to the strict standard. In this mode, code is compiled using the relaxed syntax rules, but all violations of the strict syntax rules are reported as warnings.
Use the target
option to specify the Mindcode/Mindustry Logic version to be used by the compiler and processor emulator. The target versions consist of a major and minor version number. As of now, these versions exist:
Version | Description |
---|---|
6.0 | Mindustry Logic for the latest release of Mindustry 6. |
7.0 | Mindustry Logic for the latest release of Mindustry 7. |
7.1 | As above, with slightly revised syntax of some functions. |
8.0 | Mindustry Logic for the latest BE release of future Mindustry 8. |
Version 8.0 will be the version corresponding to the official Mindustry 8 release. Future enhancements of Mindustry 8 Logic or, possibly, of the Mindcode language will be incorporated as separate versions with updated minor version number.
The target can be set using either just a major, or both major and minor version numbers. When specifying both numbers, the specified version is used. When specifying just the major version, the most recent minor version in given major category is used. Example:
#set target = 7; // Sets version 7.1
#set target = 7.0; // Sets version 7.0
To use a world-processor variant of Mindcode language, it is necessary to add W
as a suffix to the version number:
#set target = 8W; // World-processor logic version 8
#set target = 7.0W // World-processor logic version 7.0
The same names of version targets is used with the -t
/ --target
command-line option.
It is possible to set the level of individual optimization tasks. Every optimization is assigned a name, and this name can be used in the compiler directive like this:
#set expression-optimization = basic;
Most optimizations don't support the advanced
level. For those the level advanced
is the same as basic
. The complete list of available optimizations, including the option name for setting the level of given optimization and availability of the advanced optimization level is:
Optimization | Option name | Advanced |
---|---|---|
Temporary Variables Elimination | temp-variables-elimination | N |
Case Expression Optimization | case-expression-optimization | N |
Dead Code Elimination | dead-code-elimination | N |
Jump Normalization | jump-normalization | N |
Jump Optimization | jump-optimization | N |
Single Step Elimination | single-step-elimination | N |
Expression Optimization | expression-optimization | Y |
If Expression Optimization | if-expression-optimization | N |
Data Flow Optimization | data-flow-optimization | N |
Loop Hoisting | loop-hoisting | N |
Loop Optimization | loop-optimization | N |
Loop Unrolling | loop-unrolling | Y |
Function Inlining | function-inlining | N |
Case Switching | case-switching | N |
Return Optimization | case-switching | N |
Jump Straightening | return-optimization | N |
Jump Threading | jump-threading | N |
Unreachable Code Elimination | unreachable-code-elimination | N |
Stack Optimization | stack-optimization | N |
Print Merging | print-merging | Y |
This table doesn't track which optimizations provide some functionality on the experimental
level. This information is available in the individual optimization documentation.
You normally shouldn't need to deactivate any optimization, but if there was a bug in some of the optimizers, deactivating it might allow you to use Mindcode until a fix is available.
In particular, some optimizers expect to work on code that was already processed by different optimizations, so turning off some optimizations might render other optimizations ineffective. This is not a bug.
Mindcode provides a mechanism of encoding a custom instruction not known to Mindcode. Using custom instructions is useful in only a few cases:
- A new version of Mindustry (either an official release, or a bleeding edge version) creates new instructions not known to Mindcode.
- An instruction was not implemented correctly in Mindcode and a fix is not available.
- Mindustry got updated to allow new instructions created by mods, and there are mods with their own instructions.
Custom instructions may interact with Mindustry World or provide information about Mindustry World. If an instruction alters the program flow (for example, if a new function call instruction was added to Mindustry Logic), it cannot be safely encoded using this mechanism. In addition, some custom instructions might break existing optimizations through their side effects.
Custom instructions are created using the mlog()
function:
- The first argument to the
mlog
function needs to be a string literal. This literal is the instruction code. - All other arguments must be either literals, or user variables.
- String literal: the text represented by the string literal is used as an instruction argument. If the
in
modifier is used, the string literal will be used as an argument including the enclosing double quotes. - Numeric literal: all other literals must not be marked with either modifier. The primary use for numeric literals is to provide fill-in values (typically zeroes) for unused instruction parameters.
- User variable: the variable is used as an instruction argument. The argument must use the
in
andout
modifier to inform Mindcode how the corresponding instruction argument behaves:in
: the argument represents an input value - the instruction reads and uses the value of the variable.out
: the argument represents an output value - the instruction produces a value and stores it in the variable.in out
: the argument represents an input/output value - the instruction both reads and uses the input value, and then updates the variable with a new value. With a possible exception to thesync
instruction, no mlog instruction currently takes an input/output argument.
- String literal: the text represented by the string literal is used as an instruction argument. If the
Mindcode assumes that a custom instruction interacts with the Mindustry World and cannot be safely removed from the program. This is not true for instructions which only return information about the Mindustry World, but do not modify or interact with it in any way. If you want to encode such an instruction, you can use the mlogSafe()
function instead of mlog()
.
Tip
Although not strictly required, it is recommended to create an inline function with proper input/output parameters for each custom generated instruction. This way, the requirement that the mlog()
function always uses user variables as arguments can be easily met, while allowing to use expressions for input parameters in the call to the enclosing function. See the examples below.
For better understanding, the creating of custom instructions will be demonstrated on existing instructions.
The format
instruction was introduced in Mindustry Logic 8. When compiling for mindustry Logic 7, the instruction isn't available. We can create it using this code:
#set target = 7;
inline void format(value)
mlog("format", in value);
end;
param a = 10;
println("The value is: {0}");
format(a * 20);
Compiling this code produces the following output:
set a 10
print "The value is: {0}\n"
op mul __tmp0 a 20
format __tmp0
Considerations:
- The Print Merging optimization under Mindustry Logic 8 language target knows and properly handles the
format
instruction. When replaced by a custom instruction, the Print Merger won't be aware of it and might produce incorrect code. It would be necessary to turn off Print Merging optimization, if theformat
instruction was introduced in this way. - The processor emulator doesn't recognize custom instructions and won't handle them. The output produced by running the above code using the processor emulator would therefore be incorrect.
Mindustry 8 Logic adds new variants of the draw
instruction, print
being one of them. Under the Mindustry Logic 8 language target, this instruction is mapped to the drawPrint()
function. Unfortunately, this instruction takes an additional keyword argument - alignment, which needs special treatment when defining a custom instruction. Each possible value of alignment needs to be handled separately.
#set target = 7;
inline void drawPrintCenter(x, y)
mlog("draw", "print", in x, in y, "center", 0, 0, 0);
end;
inline void drawPrintBottomLeft(x, y)
mlog("draw", "print", in x, in y, "bottomLeft", 0, 0, 0);
end;
drawPrintCenter(0, 10);
drawPrintBottomLeft(0, 20);
Result:
draw print 0 10 center 0 0 0
draw print 0 20 bottomLeft 0 0 0
Considerations:
- The
draw print
instruction manipulates the text buffer and therefore interferes with the Print Merging optimization again. This optimization would need to be switched off. - A separate function for each possible alignment is required. Unused functions aren't compiled, so there isn't any cost in terms of instruction space to defining a function for each existing alignment, but it is tedious and cumbersome. Defining all possible variants for instructions that have several keyword arguments might become unfeasible.
The ucontrol getBlock
instruction is an example of instruction which has output parameters. Also, we know it is an instruction which doesn't modify the Mindustry World and therefore is safe. Had it not be known by Mindcode, it could be defined like this:
// Using 'getBlock2' as a name to avoid clashing with the existing function name
inline def getBlock2(x, y, out type, out floor)
mlogSafe("ucontrol", "getBlock", in x, in y, out type, out building, out floor);
return building;
end;
x = floor(rand(100));
y = floor(rand(200));
// These two instruction generate the same mlog code:
building = getBlock(x, y, out type, out floor);
print(building, type, floor);
building = getBlock2(x, y, out type, out floor);
print(building, type, floor);
compiles to
op rand __tmp0 100 0
op floor x __tmp0 0
op rand __tmp2 200 0
op floor y __tmp2 0
ucontrol getBlock x y type building floor
print building
print type
print floor
ucontrol getBlock x y __fn0_type __fn0_building __fn0_floor
print __fn0_building
print __fn0_type
print __fn0_floor
The jump
instruction is a control flow instruction, an as such producing it through the mlog()
function will break the compiled code. Anyhow, Mindcode will allow the following code to be compiled:
mlog("jump", 50, "always");
producing
jump 50 always
Considerations:
- There aren't direct ways to obtain the targets for the
jump
instruction. Forcing the instruction to target the intended code might be very difficult (albeit not outright impossible when modifying the instruction to target labels instead). - Introducing jumps unrecognized by Mindcode would render most of the compiled code unsafe. Mindcode uses the knowledge of the program control flow to generate the code and make various optimizations. Introducing unrecognized control flow instructions would mean the compiled code and especially the optimizations are not correct.
« Previous: Functions | Up: Contents | Next: Code optimization »