Start here if you want to work on MimIR itself. This page is the contributor entry point for build, test, style, and debugging workflow. For API and IR usage patterns, continue with the Developer Guide.
Contributor Quick Start
If you do not have a GitHub account set up with SSH, you can clone MimIR via HTTPS instead:
For day-to-day development, a good default is:
Useful follow-up commands are:
The following CMake switches are available:
| CMake Switch | Options | Default | Comment |
|---|---|---|---|
| CMAKE_BUILD_TYPE | Debug | Release | RelWithDebInfo | Debug | Build type. |
| CMAKE_INSTALL_PREFIX | /usr/local | Install prefix. | |
| BUILD_SHARED_LIBS | ON | OFF | ON | If ON, build shared libraries. |
| MIM_BUILD_DOCS | ON | OFF | OFF | If ON, build the documentation (requires Doxygen). |
| MIM_BUILD_EXAMPLES | ON | OFF | OFF | If ON, build the examples. |
| MIM_BUILD_LL_RUNTIME | ON | OFF | ON | If ON, compile the ll backend's C runtime wrappers to LLVM IR (requires clang). |
| MIM_BUILD_PYTHON | ON | OFF | ON | If ON, build Python bindings. |
| MIM_CLANG | <path/to/clang> | autodetected | clang used to compile the ll backend's C runtime wrappers. |
| MIM_DEBUG_OPTIMIZE | ON | OFF | ON | If ON, compile Debug builds with -Og instead of -O0: same assertions and checks, but roughly 6x faster. Switch OFF when stepping through code, as -Og optimizes away some locals. |
| MIM_ENABLE_CHECKS | ON | OFF | ON | If ON, enable expensive runtime checks (requires CMAKE_BUILD_TYPE=Debug). |
| MIM_LLVM_LINK | <path/to/llvm-link> | autodetected | llvm-link used to link C runtime wrappers consisting of several files. |
| MIM_VER_SUFFIX | <suffix> | -dev | Suffix appended to the version string; use "" for a release. |
| MIM_VERIFY_PLUGINS | ON | OFF | ON | If ON, elaborate every plugin's .mim with its plugin library loaded after building it. --bootstrap alone never loads the libraries, so it cannot catch errors that need the normalizers. |
| BUILD_TESTING | ON | OFF | OFF | If ON, build all unit tests and lit tests. |
| MIM_FILECHECK | <filecheck_cmd> | autodetected | FileCheck command used by the lit tests. (requires BUILD_TESTING=ON). |
| MIM_LIT_TIMEOUT | <timeout_in_sec> | 120 | Timeout for lit tests. (requires BUILD_TESTING=ON). |
| MIM_LIT_WITH_VALGRIND | ON | OFF | OFF | If ON, run the Mim CLI in the lit tests under Valgrind. (requires BUILD_TESTING=ON). |
| MIM_TEST_ROUNDTRIP | ON | OFF | ON | If ON, test-all also checks that -o and --output-ast round-trip. (requires BUILD_TESTING=ON). |
Dependencies
In addition to the provided submodules, you will need:
Mim emits LLVM IR, but it does not link against LLVM. So you can simply hand the emitted *.ll file to your system's LLVM toolchain. Strictly speaking, LLVM is not required unless you want to continue from emitted LLVM IR.
Testing
Run every in-tree test suite with:
- See also
- Third-Party Plugin Discovery
- Note
- A third-party plugin in extra/ contributes to both suites automatically.
lit Tests
Run the lit test suite with:
You can also invoke the lit tests manually and filter for a specific test:
If your build directory is actually called build, you can also use probe.sh:
- Note
- To generate a one-line reproducer for the current checkout and a specific lit failure, use:
Round-Trip Tests
These check that re-reading the output of mim -o and mim --output-ast for each lit input reproduces it byte for byte:
test-all includes them unless MIM_TEST_ROUNDTRIP=OFF.
Unit Tests
test/ holds the doctest unit tests, built as mim-test and mim-regex-test. Run them - and every other CTest test - with:
You can additionally enable Valgrind via:
During debugging, you will usually want to run only a specific test case or subcase. Both filters accept * wildcards:
doctest breaks into an attached debugger on a failing assertion; pass -nb to suppress that.
- Note
- To generate a one-line reproducer for the current checkout and a specific unit-test failure, use:
Coding Style
C++ Style
Use the following coding conventions:
- Class/type names use CamelCase.
- Constants defined in an enum or via static const use Camel_Snake_Case.
- Macro names use SNAKE_IN_ALL_CAPS.
- Everything else - variables, functions, etc. - uses snake_case.
- Use a trailing underscore for private_or_protected_member_variables_.
- Methods/functions that return a bool should be prefixed with is_.
- Methods/functions that return a std::optional or a pointer that may be nullptr should be prefixed with isa_.
- Do not do this for a public_member_variable.
- Use struct for plain old data.
- Use class for everything else.
- Prefer // C++-style comments over /* C-style comments */.
- Use #pragma once as the include guard for headers.
- Order visibility groups like this:
- public
- protected
- private
Include Style
Use #include "..." for headers that belong to the artifact you are currently building and #include <...> for everything you link against - including external dependencies such as ankerl, fe, and the standard library.
The artifact a file belongs to - and hence the prefix of its own headers - follows from its path:
| Path | Artifact | Own headers |
|---|---|---|
| src/mim/..., include/mim/... | libmim | "mim/..." |
| src/automaton/..., include/automaton/... | libautomaton | "automaton/..." |
| src/mim/plug/X/..., include/mim/plug/X/... | plugin X | "mim/plug/X/..." |
| extra/X/... | out-of-tree plugin X | "mim/plug/X/..." |
| src/mim/cli/... | the mim CLI | - |
| test/... | the unit tests | - |
| py/bindings/... | the Python bindings | - |
The last three link against libmim but do not ship headers of their own, so they use <...> throughout. A plugin is a separate build artifact as well, so for a plugin X:
The generated autogen.h counts as part of the plugin, while another plugin's autogen.h does not.
The main upshot is that in-tree plugins in src/mim/plug/X and out-of-tree plugins in extra/X spell their includes in exactly the same way. Hence, scripts/extract_plugin.py can move a plugin out of the tree without touching a single #include, and an in-tree plugin exercises the same public header surface that an external plugin sees.
This is checked by scripts/check_includes.py, which is also wired up as a pre-commit hook:
- Note
- --fix only changes "..." to <...> and vice versa. Since the two forms belong to different include groups, run pre-commit run clang-format --files <fixed files> afterwards to regroup them. Only path-qualified includes are checked, so a plain #include "foo.h" next to the including file is always fine.
Doxygen Style
- Use /// for Doxygen comments.
- Use Markdown-style Doxygen comments.
- Group related functions, methods, etc. via named member groups.
- Capitalize the group name unless it is directly named after a method.
For all the smaller details such as indentation width, use clang-format and the provided .clang-format file in the repository root.
To run clang-format automatically on changed files, install the provided pre-commit hook:
You can also disable clang-format for a piece of code when necessary. Depending on your editor, the Vim integration and similar plugins may also be useful.
Example
Here is a small header that follows the conventions above:
CMake Style
- Use 4 spaces for one indentation level.
- Prefer lowercase CMake commands such as set, if, foreach, and add_subdirectory.
- Use uppercase names for project-specific CMake variables and options such as MIM_BUILD_DOCS or MIM_PLUGINS.
- For longer CMake calls, put arguments on separate indented lines instead of cramming everything onto one line.
Markdown Style
- Use one sentence per line in Markdown prose.
- Do not hard-wrap Markdown text to 80 columns.
Mim Coding Style
- Prefer the primary UTF-8 surface syntax over ASCII-only spellings when writing Mim code and tests.
- Prefer 4 spaces for one indentation level, but aligned layouts may use different spacing when that makes the code clearer.
- Write callable declarations with parenthesized domain groups, for example lam foo (x: X) (y: Y): Z = ....
- Prefer group patterns lam foo (a b c: T) over lam foo (a: T, b: T, c: T)
- Separate curried domain groups with a space, and write the return type as ): Z, with no space before : and one space after it.
- Use spaces after commas in lists and groups: a, b, c.
- Write binder ascriptions as x: X, but keep literal and bottom ascriptions tight: 23:T.
- Use snake_case for value-level names such as functions, lambdas, binders, local lets, and pattern-bound values.
- Use CamelCase for type-level names such as types and type constructors.
Syntax Highlighting
- Mim (Neo)Vim Plugin: Vim syntax highlighting and abbreviations for Unicode chars.
- Tree Sitter Mim: Neovim syntax highlighting (can be used in combination with the Vim plugin).
- Helix fork of the tree sitter repo above.
Debugging
The Testing section above already provides some debugging tips. This section has more information about this topic.
See also:
- Command-Line Reference
- GDB: A quick guide to make your debugging easier
- Advanced GDB Usage
- Debugging with GDB
Dumping
You can directly invoke several MimIR dump helpers from within GDB, for example:
In particular, note the different output levels of mim::Def::dump.
You can also tweak the output behavior directly from within GDB by changing mim::World::flags or mim::World::log:
Another useful trick is to recover a Def* from a mim::Def::gid via mim::World::gid2def:
Displaying DOT Graphs
scripts/xdot.gdb provides custom GDB commands to generate a DOT graph and display it with xdot.
To enable it, source scripts/xdot.gdb from your ~/.gdbinit:
Here is the xdot GDB command in action:
Conditional Breakpoints
Often, you will want to inspect a specific mim::Def at a particular point in the program. Conditional breakpoints are very handy for this.
For example, the following command breaks if the mim::Def::gid of variable def is 42 at source location foo.cpp:23:
Catching Exceptions
For several things, such as errors in the Mim frontend, MimIR uses C++ exceptions for error handling. To stop when an exception is thrown, use:
Valgrind & GDB
If you run into memory-related problems, it can be useful to run the program with Valgrind's GDB server.
Launch the test binary like this:
and then follow the instructions printed by Valgrind.
Logging
Each -V raises the log level by one; MimIR logs to stderr:
| Flag | fe::Log::Level |
|---|---|
| none | Error |
| -V | Warn |
| -VV | Info |
| -VVV | Verbose |
| -VVVV | Debug |
| -VVVVV | Trace |
Debug and Trace output only exists in a Debug build.
Triggering Breakpoints
You can tell mim to trigger a breakpoint when certain events happen:
See the Command-Line Reference for the full list of flags.
Profiling
If a compilation is taking longer than expected, use --profile to find out which mim::Phase is responsible. It measures wall-clock time spent in each phase and reports it via --output-profile <file> (or - for stdout) in one of three formats:
Once --profile has enabled profiling, --output-profile defaults to -; conversely, --output-profile <file> alone implies --profile trace. An unknown <mode> is an error.
The trace format dumps Chrome Trace Event Format JSON. Load the resulting file into chrome://tracing (see the official guide), into Perfetto, or into speedscope to inspect it as a timeline.
See fe::Profiler in the fe submodule for implementation details.
