Source in.
Machine code out.
Nothing in between.

Myrissa is a Pascal-family systems language whose compiler owns the whole stack. It parses, optimizes, encodes x86_64, writes PE and ELF images, links archives and injects its own runtime. One executable. No LLVM, no Clang, no external linker.

hello.myr
module exe hello;
begin
  println("Hello, Myrissa!");
end.

> myr hello -r
Hello, Myrissa!
hello.exe
00000000  4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
00000010  B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00
00000080  50 45 00 00 64 86 05 00 00 00 00 00 00 00 00 00
Every byte in the output was written by Myrissa.PE64

What you do not install

  • LLVM
  • Clang
  • GCC
  • MSVC
  • link.exe
  • ld
  • a C runtime to ship
  • is the whole toolchain
Two targets, one machine
Win64 PE and Linux64 ELF come out of the same compiler on the same Windows host. Cross-compiling is the normal case. With WSL installed, the Linux binary runs with one flag.
Three artifact kinds, both targets
Executables, dynamic libraries (DLL, SO) and static libraries (COFF .lib, ELF .a). The output kind is set by the module declaration, not by build flags.
A linker of its own
Consumes COFF and ELF archives, resolves symbols across multi-member archives, chases DEFAULTLIB into SDK import libraries, and links foreign MSVC and MinGW archives.
A runtime it generates itself
Refcounted strings, dynamic arrays, heap tracking with leak reports, structured exceptions with hardware fault recovery, and a unit-test harness, all built through the same IR API a frontend uses.

Pascal clarity. C ABI all the way down.

Exact-width types, records that lay out like C structs, managed strings, sets as bitmasks, real exceptions, and a module system that keeps every symbol qualified. Calling C is a declaration, not a binding layer.

module exe shapes;

type
  Color = choices(red, green, blue = 5, alpha);

  Point = record
    x: float64;
    y: float64;
  end;

  Shape = record
    origin: Point;
    color:  Color;
    name:   string;
  end;

routine hsv_to_rgb(const h: float32; var r: uint8; var g: uint8; var b: uint8);
begin
  r := uint8(h * 255.0);
  g := uint8((1.0 - h) * 255.0);
  b := 128;
end;

begin
  var s: Shape = Shape(origin: Point(x: 10.0, y: 20.0), color: Color.blue, name: "box");
  var r: uint8;
  var g: uint8;
  var b: uint8;
  hsv_to_rgb(0.25, r, g, b);
  println("%s at (%f, %f) rgb(%d, %d, %d)", s.name, s.origin.x, s.origin.y, r, g, b);
end.
Records support inheritance, packed layout, explicit alignment, overlays and bit fields. Output goes straight to printf, so the format string is C's.
module exe interop;

// the same routine, bound to the C runtime of each target
@ifdef TARGET_WIN64
routine clink myabs(const n: int32): int32; external "msvcrt.dll" name "abs";
@elseif TARGET_LINUX64
routine clink myabs(const n: int32): int32; external "libc.so.6" name "abs";
@endif

// a Windows-only system DLL
@ifdef TARGET_WIN64
routine clink GetTick(): uint64; external "kernel32" name "GetTickCount64";
@endif

// a generated binding: import SDL3 and call it qualified
import SDL3;

begin
  println("abs(-42) = %d", myabs(-42));
  @ifdef TARGET_WIN64
  println("tick = %llu", GetTick());
  @endif
  SDL3.SDL_Init(SDL3.SDL_INIT_VIDEO);
  SDL3.SDL_Quit();
end.
Naming a library in an external clause is what links it. A bare name probes for a static archive first, then a DLL or shared object. The C runtime is named per target under @ifdef; CImporter generates the SDL3 and raylib units from their headers.
module dll mathlib;

public var call_count: int32 = 0;

public routine clink fast_add(const a: int32; const b: int32): int32;
begin
  call_count += 1;
  return a + b;
end;

initialize
  println("mathlib loaded");
end;

finalize
  println("mathlib unloaded after %d calls", call_count);
end;

end.
clink exports an unmangled symbol any language can call. Change the first line to module lib and the same source becomes a static library. Both build for win64 and linux64.
routine safe_div(const a: int32; const b: int32): int32;
begin
  guard
    return a div b;          // hardware divide-by-zero is caught
  except
    println("caught %s (code %lld)", cstr(excmsg()), exccode());
    return -1;
  finally
    println("always runs");
  end;
end;

routine load(const path: string);
begin
  if len(path) = 0 then
    throwcode(404, "empty path");   // propagates across routines, modules and static libs
  end;
end;
SEH with real unwind info on Windows, signal-based on Linux. Software throws and hardware faults land in the same except block on both targets.
module exe mathlib;
@unittestmode on;

routine add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

end.

test "add returns the sum"
begin
  asserteq(5, add(2, 3));
  asserteq(-2, add(-5, 3));
end;

test "float tolerance"
begin
  asserteqf(3.14, 22.0 / 7.0, 0.01);
end;
The test runner replaces the entry point. Assertions are non-aborting; the process exit code is the number of failures.

One process, no temporary files, no child processes

Every stage is Delphi code in the same repository. The output of the last one is the binary.

  1. Lexer

    Tokenizes .myr source. Keywords and the sixteen primitive types are registered, not hardcoded.

  2. Parser

    Recursive descent for declarations and statements, Pratt for expressions. Produces a complete AST.

  3. Semantic analysis

    Resolves every type, symbol, cross-module reference and directive once, and records it on the AST. Later stages read; they never reconstruct.

  4. Emitter

    Walks the enriched AST and drives the backend's fluent IR builder. All language-specific lowering lives here.

  5. SSA optimizer

    Constant propagation and folding, copy propagation, common subexpression elimination, dead code elimination, unreferenced-function removal. Level-gated: none, basic, full.

  6. x86_64 code generation

    Linear-scan register allocation and direct byte encoding, REX, ModRM, SIB, SSE2. Win64 and System V ABIs chosen per target. No assembler pass.

  7. Image writer and linker

    PE with .pdata unwind info, version resources and icons. ELF. COFF and ELF object and archive writers. A linker that reads foreign archives.

  8. Runtime

    Injected into every module through the same IR API a frontend uses. Strings, arrays, exceptions, heap tracking, tests.

  9. hello.exe

    or hello, hello.dll, libhello.so, hello.lib, libhello.a

The language at a glance

Exact-width types

int8 to uint64, float32, float64, char, wchar, boolean, pointer. Every one maps to a C type with no conversion.

Managed text

string is UTF-8 and wstring is UTF-16, both refcounted. cstr() and wstr() borrow a raw pointer for C calls without a copy.

Records like C structs

Inheritance, packed, align(n), overlays (unions), anonymous overlays for tagged unions, bit fields, and named record literals.

Routines

One keyword for functions and procedures. const and var parameters, local sections, first-class routine types, overloading via cpplink, forward declarations, variadics with varargs.

Control flow

if, while, for and downto, repeat, match with ranges and lists, break, continue, compound assignment.

Sets

Stack bitmasks of up to 64 elements. Union, intersection, difference and membership are single instructions.

Modules

exe, dll, lib, unit. Private by default, public to export, always qualified on import, initialize and finalize on every kind.

Directives

@target, @optimize, @subsystem, @addverinfo, @exeicon, @copydll, @ifdef family with MYRISSA, TARGET_WIN64, TARGET_LINUX64, DEBUG, RELEASE.

Every kind, every target, every optimization level

Cross-target parity is a hard invariant. The compliance gate runs each suite on both targets at all three levels and requires zero assertion failures and zero heap leaks before anything ships.

Modulewin64linux64Proven by
exename.exenameEXE and UNITTEST suites
dllname.dllname.soDLL suite, two DLLs in one process
libname.libname.aLIB and LINK suites, lib to lib, SEH across libs, foreign MSVC and MinGW archives
unitcompiled inline into the importerUNIT suite, cross-module symbols and exceptions

Three executables, one embedded compiler

myr the compiler

Build, run, cross-compile, or launch the debugger in one command. Pass the source name without the extension: myr hello -r -t linux64 -opt full.

myr cimport bindings from C headers

A .mys script names the header, include paths, exclusions, renames and per-target DLLs; CImporter writes a complete unit module. raylib and SDL3 ship this way.

myrlsp language server

Diagnostics as you type, completions, hover, go-to-definition and document symbols for any LSP-capable editor. The same service runs in-process.

Native debugger over DAP

A debug build writes a compact .mdbg sidecar with functions, lines, variable locations and every @breakpoint;. No PDB, no DWARF. Breakpoints, stepping, variables, call stacks, in VS Code or any DAP client.

myrtester the gate

Runs every compliance suite across both targets and all three optimization levels. Rebuilds DLL and lib producers before their consumers at each level.

SSA dumps

The -ds flag writes the optimizer's view of every function. When something looks wrong there is no IR from someone else to decode; it is all right there.

Watch and listen

Myrissa infographic
Use the expand button to view full size

Start in under a minute

  1. Download the release and put myr.exe on your PATH. That is the install.
  2. Save the program on the right as hello.myr.
  3. Run myr hello -r. Add -t linux64 for a Linux binary; with WSL installed it runs immediately.
  4. Open the documentation for the language reference, grammar, interop guide and recipes.

Download

terminal
> myr hello -r
Hello, Myrissa!

> myr hello -r -t linux64
Hello, Myrissa!

> myr hello -r -opt full
Hello, Myrissa!

> myr hello -d
debugger attached, waiting at @breakpoint