This documentation is also published as Markdown for efficient machine reading: the whole site is indexed at /llms.txt, and every page has a clean Markdown copy at the same URL with .md appended. These are generated from the same source and cost far fewer tokens to read than this rendered HTML.

Skip to main content Skip to navigation
Compiler

Compiler

Mew code goes through five steps on its way to a .NET assembly.

mermaid
flowchart LR;
    subgraph Frontend
    AST-->HIR
    HIR-->MIR
    MIR-->LIR
    end
    subgraph Backend
    LIR-->MSIL["MSIL"]
    end
    MSIL-->Assembly[".NET assembly"]

The backend writes MSIL, and it is reached through LIR.

Here is a small program at every stage, from the source to the IL the backend wrote:

use std;
  
fn square(x: i32) -> i32 {
    return x * x;
}
  
for i in 1..5 {
    println($"{square(i)}");
}
<SyntaxTree>
  <CompilationUnit>
    <Use Name="std" />
    <FunctionDeclaration Name="square" ReturnType="i32">
      <Parameters>
        <ParameterSyntax Name="x" Type="i32" />
      </Parameters>
      <Body>
        <Block>
          <Return>
            <Binary Operator="Multiply">
              <Left>
                <Identifier Name="x" />
              </Left>
              <Right>
                <Identifier Name="x" />
              </Right>
            </Binary>
          </Return>
        </Block>
      </Body>
    </FunctionDeclaration>
    <For Name="i">
      <Collection>
        <Range>
          <Start>
            <Numeral Kind="Integer" Value="1" />
          </Start>
          <End>
            <Numeral Kind="Integer" Value="5" />
          </End>
        </Range>
      </Collection>
      <Body>
        <Block>
          <ExpressionStatement>
            <FunctionCall Name="println">
              <Arguments>
                <ArgumentSyntax>
                  <InterpolatedString Text="">
                    <InterpolationSyntax Text="">
                      <FunctionCall Name="square">
                        <Arguments>
                          <ArgumentSyntax>
                            <Identifier Name="i" />
                          </ArgumentSyntax>
                        </Arguments>
                      </FunctionCall>
                    </InterpolationSyntax>
                  </InterpolatedString>
                </ArgumentSyntax>
              </Arguments>
            </FunctionCall>
          </ExpressionStatement>
        </Block>
      </Body>
    </For>
  </CompilationUnit>
</SyntaxTree>
fn <global>.square(x: i32) -> i32 {
    return x * x;
}
  
for i: i32 in new Range {
    start: 1i32,
    end: 5i32,
} {
    println(concat(to_string<i32>(square(i))));
}
fn <global>.square(x: i32) -> i32 {
    return x * x;
}
  
let mut $at: i32 = 1i32;
let $end: i32 = 5i32;
goto check2;
body1:
let i: i32 = $at;
println(concat(to_string<i32>(square(i))));
continue3:
$at += 1i32;
check2:
if $at < $end goto body1;
break3:
fn square(x: i32) -> i32
    .maxstack 2
    ldarg   0
    ldarg   0
    mul
    ret
  
main
    .maxstack 2
    .local 0 $at: i32
    .local 1 $end: i32
    .local 2 i: i32
    ldc.i4  1
    stloc   0
    ldc.i4  5
    stloc   1
    br      check2
body1:
    ldloc   0
    stloc   2
    ldloc   2
    call    square
    box     i32
    tostr   i32
    call    println
continue3:
    ldloc   0
    ldc.i4  1
    add
    stloc   0
check2:
    ldloc   0
    ldloc   1
    clt
    brtrue  body1
break3:
    ldc.i4  0
    ret
.class private auto ansi '<Module>'
{
} // end of class <Module>
  
.class public auto ansi abstract sealed beforefieldinit MewApp.Functions
	extends [System.Runtime]System.Object
{
	// Methods
	.method public hidebysig static 
		int32 square (
			int32 x
		) cil managed 
	{
		// Method begins at RVA 0x2050
		// Header size: 1
		// Code size: 4 (0x4)
		.maxstack 8
  
		IL_0000: ldarg.0
		IL_0001: ldarg.0
		IL_0002: mul
		IL_0003: ret
	} // end of method Functions::square
  
} // end of class MewApp.Functions
  
.class public auto ansi abstract sealed beforefieldinit MewApp.Program
	extends [System.Runtime]System.Object
{
	// Methods
	.method public hidebysig static 
		int32 '<entry>' () cil managed 
	{
		// Method begins at RVA 0x2058
		// Header size: 12
		// Code size: 46 (0x2e)
		.maxstack 2
		.locals init (
			[0] int32,
			[1] int32,
			[2] int32
		)
  
		IL_0000: ldc.i4.1
		IL_0001: stloc.0
		IL_0002: ldc.i4.5
		IL_0003: stloc.1
		IL_0004: br.s IL_0026
		// loop start (head: IL_0026)
			IL_0006: ldloc.0
			IL_0007: stloc.2
			IL_0008: ldloc.2
			IL_0009: call int32 MewApp.Functions::square(int32)
			IL_000e: box [System.Runtime]System.Int32
			IL_0013: call class [System.Runtime]System.Globalization.CultureInfo [System.Runtime]System.Globalization.CultureInfo::get_InvariantCulture()
			IL_0018: call string [System.Runtime]System.Convert::ToString(object, class [System.Runtime]System.IFormatProvider)
			IL_001d: call void [Mew.Std]std.Functions::println(string)
			IL_0022: ldloc.0
			IL_0023: ldc.i4.1
			IL_0024: add
			IL_0025: stloc.0
  
			IL_0026: ldloc.0
			IL_0027: ldloc.1
			IL_0028: clt
			IL_002a: brtrue.s IL_0006
		// end loop
  
		IL_002c: ldc.i4.0
		IL_002d: ret
	} // end of method Program::'<entry>'
  
	.method public hidebysig static 
		int32 Main () cil managed 
	{
		// Method begins at RVA 0x2094
		// Header size: 12
		// Code size: 24 (0x18)
		.maxstack 1
		.entrypoint
		.locals init (
			[0] int32
		)
  
		.try
		{
			IL_0000: call int32 MewApp.Program::'<entry>'()
			IL_0005: stloc.0
			IL_0006: leave IL_0016
		} // end .try
		catch [System.Runtime]System.Exception
		{
			IL_000b: call int32 [Mew.Runtime]Mew.Runtime::Report(class [System.Runtime]System.Exception)
			IL_0010: stloc.0
			IL_0011: leave IL_0016
		} // end handler
  
		IL_0016: ldloc.0
		IL_0017: ret
	} // end of method Program::Main
  
} // end of class MewApp.Program
using System;
using System.Globalization;
using Mew;
using std;
  
namespace MewApp;
  
public static class Functions
{
	public static int square(int x)
	{
		return x * x;
	}
}
public static class Program
{
	public static int <entry>()
	{
		int i = 1;
		for (int num = 5; i < num; i++)
		{
			Functions.println(Convert.ToString((object)Functions.square(i), (IFormatProvider)CultureInfo.InvariantCulture));
		}
		return 0;
	}
  
	public static int Main()
	{
		try
		{
			return <entry>();
		}
		catch (Exception error)
		{
			return Runtime.Report(error);
		}
	}
}

1. AST parsing

The parsing step iterates through all source files, and builds a syntax tree for each of them.
The syntax tree represents the code as it was written, maintaining the trivia such as white space, comments etc.

Each node in the AST has a reference to both its parent and children.

Apart from being the basis for HIR generation, the AST is also used to interact with the source code programmatically, i.e. from the LSP server.

2. HIR generation

HIR, short for High-level Intermediate Representation, is a bound tree, where all types are known.

The HIR references resolved symbols for the different parts of Mew (namespaces, types, functions, parameters, variables etc). For example, two code blocks that call a function will have the same symbol reference to that function.

  1. Build symbol table
    1. Namespaces
    2. Types
    3. Free functions
    4. Type members
  2. Binding
    1. Types
    2. Free functions
    3. Top level statements

Important

HIR might contain errors, represented as error symbols.

3. MIR generation

MIR, short for Medium-level Intermediate Representation, is a lowered HIR, without constructs such as while/loop/if.

  • All higher-level constructs such as loops and conditions have been lowered into labels and branches.
  • Control flow analysis and some optimizations are done here as well.

Important

MIR might contain errors, represented as error symbols.

4. LIR generation

LIR, short for Low-level Intermediate Representation, is a lowered MIR, shaped like the instructions that will be emitted.

Where MIR is still a tree, LIR is a flat list per method over an evaluation stack. It numbers the locals, makes every conversion and every box explicit, resolves which member a name meant, and turns a lambda into a type. It is still typed with the compiler's own symbols rather than with .NET's, so it can be read back and compared in a test.

Warning

LIR MUST NOT contain any errors.

A function the lowering cannot finish is recorded, and the backend refuses the whole program instead of emitting a module with a hole in it.

5. Writing the assembly

The backend turns LIR into a .NET assembly with System.Reflection.Metadata, writing the IL itself rather than going through another language. It is written to a .mew directory beside the file the program starts from, together with the runtime configuration the host reads. The .NET SDK is not involved, and neither is a project file.

Nothing above LIR knows how a program is written out, so the language is not defined in terms of what .NET does. The symbol model does read .NET metadata, so a program can name a platform type.

A debug database is written beside the assembly, so a debugger can stop on a line of Mew.

Mew is a programming language under construction.