Skip to content

Compiler Reference

← Back to Documentation

Complete reference for the Sushi compiler: CLI options, optimization levels, and error codes.

Table of Contents

Command Line Interface

Basic Usage

./sushic [options] <source-files> [-o output]

Examples

# Compile single file (output: hello)
./sushic hello.sushi

# Specify output name
./sushic program.sushi -o myprogram

# Multiple files (multi-unit project)
./sushic main.sushi utils.sushi -o app

# With optimization
./sushic --opt O3 program.sushi -o optimized

Options

Option Description
-o NAME Specify output executable name
--opt LEVEL Set optimization level (none, mem2reg, O1, O2, O3)
--lib Compile to library bitcode instead of executable
--traceback Show full Python traceback on errors
--dump-ast Print abstract syntax tree
--dump-ll Print LLVM IR to terminal
--write-ll Write LLVM IR to <output>.ll file
--no-incremental Force full rebuild, ignoring cached object files
--clean-cache Remove __sushi_cache__/ directory and exit
--cache-dir PATH Custom cache directory location

Library Compilation

# Compile source to reusable library
./sushic --lib mylib.sushi -o mylib.slib

# Inspect library metadata
./sushic --lib-info mylib.slib

Libraries are used via use <lib/...> statements in source code:

use <lib/mylib>

fn main() i32:
    # Library functions/types are now available
    return Result.Ok(0)

See Libraries for complete documentation.

Incremental Compilation

Multi-unit projects (those with use statements to other .sushi files) automatically use incremental compilation. Each unit is compiled to its own .o file and cached in __sushi_cache__/. Only units whose content or dependencies change are recompiled.

# First build: compiles all units
./sushic main.sushi

# Second build: reuses cached .o files (near-instant codegen)
./sushic main.sushi

# Force full rebuild
./sushic --no-incremental main.sushi

# Clean cache and exit
./sushic --clean-cache

# Custom cache directory
./sushic --cache-dir /tmp/my_cache main.sushi

How it works: - Semantic analysis always runs whole-program (fast, pure Python) - After analysis, a content-based fingerprint is computed per unit - If the fingerprint matches the cached .o file, codegen is skipped - Stdlib and library imports are also cached as .o files - All .o files are linked together at the end

Cache invalidation triggers: - Source file content changes - Dependency public API changes (transitive) - Compiler version mismatch - Optimization level change - Platform/target triple change

Output example:

Code generation:
  main                           [cached]
  helpers/math                   [rebuilt]
  helpers/strings                [cached]

Codegen: 3 units (2 cached, 1 rebuilt) in 0.05s
Linking: 3 units + 1 stdlib in 0.03s
Success! Wrote native binary: main

Notes: - Single-file programs skip incremental compilation entirely - --dump-ll forces the monolithic (non-incremental) path - --write-ll is not supported in incremental mode - The cache directory (__sushi_cache__/) is already in .gitignore

Optimization Levels

Sushi provides a complete LLVM optimization pipeline with multiple levels.

Overview

Level Description Use Case Compile Time
none / O0 No optimization Debugging, development Fastest
mem2reg SROA only (default) Quick builds with SSA Very fast
O1 Basic optimizations Fast compilation + improvements Fast
O2 Moderate optimizations Recommended for production Moderate
O3 Aggressive optimizations Maximum performance Slower

Default Behavior

If no --opt flag is specified, mem2reg is used (basic SROA for SSA form).

mem2reg (Default)

What it does: - SROA (Scalar Replacement of Aggregates) - promotes stack allocations to registers - Converts code to SSA (Static Single Assignment) form - Minimal overhead, fast compilation

Use when: - Quick development builds - Testing and iteration - SSA form needed for debugging

./sushic program.sushi
# Equivalent to:
./sushic --opt mem2reg program.sushi

O1 - Basic Optimizations

What it does: - All of mem2reg, plus: - CFG simplification - removes redundant branches - Instruction combining - peephole optimizations - Dead code elimination - removes unused code and variables - Basic constant folding

Use when: - Development with some performance - Testing with realistic speed - Fast CI/CD builds

./sushic --opt O1 program.sushi

Performance impact: 10-30% faster than none, minimal compile time increase.

What it does: - All of O1, plus: - SCCP (Sparse Conditional Constant Propagation) - Loop optimizations (rotation, simplification, deletion) - GVN (Global Value Numbering) - eliminates redundant computations - Memory optimizations (memcpy optimization, dead store elimination) - Jump threading - optimizes conditional branches - Tail call elimination - Interprocedural optimizations (IPSCCP, dead argument elimination)

Use when: - Production builds - Benchmarking - Deployment

./sushic --opt O2 program.sushi -o production

Performance impact: 50-200% faster than none, moderate compile time.

O3 - Aggressive Optimizations

What it does: - All of O2, plus: - Aggressive loop unrolling - unrolls loops for better performance - Loop strength reduction - optimizes loop induction variables - Aggressive instruction combining - Instruction sinking - moves instructions for better register allocation - Argument promotion - converts by-reference to by-value where beneficial - Function merging - combines identical functions

Use when: - Maximum performance required - Benchmarking - Performance-critical production code

./sushic --opt O3 program.sushi -o maximum_performance

Performance impact: 100-300% faster than none, longest compile time.

Optimization Examples

Example program impact:

Level       IR Lines    Binary Size    Relative Speed
none (O0)   278         34,016 bytes   1.0x (baseline)
mem2reg     245         34,000 bytes   1.1x
O1          220         33,980 bytes   1.3x
O2          195         33,968 bytes   1.8x
O3          181         33,960 bytes   2.2x

Real optimizations observed:

# Source code
let i32 x = 10
let i32 y = (x + 5) * 2

# After O2/O3 constant folding
let i32 y = 30  # Computed at compile time

Viewing Optimized Code

# Save LLVM IR to file
./sushic --opt O3 --write-ll program.sushi
cat program.ll

# Print IR to terminal
./sushic --opt O3 --dump-ll program.sushi

Recommendations

Development:

# Fastest iteration
./sushic --opt none program.sushi

# Or default (mem2reg)
./sushic program.sushi

Testing:

# Balance of speed and compile time
./sushic --opt O1 program.sushi

Production:

# Recommended for most deployments
./sushic --opt O2 program.sushi -o app

# Maximum performance
./sushic --opt O3 program.sushi -o app

Benchmarking:

# Always use O3 for true performance measurement
./sushic --opt O3 benchmark.sushi -o bench
./bench

Debugging Options

Full Traceback

Show complete Python stack trace on compiler errors:

./sushic --traceback program.sushi

When to use: - Reporting compiler bugs - Understanding internal errors - Debugging compiler itself

Dump AST

Print the abstract syntax tree:

./sushic --dump-ast program.sushi

Output includes: - Parsed AST structure - Type annotations - Scope information

When to use: - Understanding parsing - Debugging grammar issues - Compiler development

Display generated LLVM IR:

./sushic --dump-ll program.sushi

When to use: - Understanding code generation - Verifying optimizations - Learning LLVM IR - Performance debugging

Save LLVM IR to File

Save IR to <output>.ll:

./sushic --write-ll program.sushi
cat program.ll

# With custom output name
./sushic --write-ll program.sushi -o myapp
cat myapp.ll

When to use: - Analyzing optimized code - Sharing IR for debugging - Comparing optimization levels

Combining Debug Options

# Full debug output
./sushic --traceback --dump-ast --dump-ll program.sushi

# Save IR with optimization
./sushic --opt O3 --write-ll program.sushi

Error Codes

Sushi uses structured error codes for diagnosing issues.

Error Code Format

  • CE0xxx: Internal/function errors
  • CE1xxx: Scope/variable errors
  • CE2xxx: Type/array/struct errors
  • CE3xxx: Unit management errors
  • CWxxxx: Warnings
  • RExxxx: Runtime errors

Common Errors

CE1001: Undeclared Identifier

fn main() i32:
    # ERROR CE1001: use of undeclared identifier 'x'
    println(x)
    return Result.Ok(0)

Fix: Declare variable with let before use.

CE1002: Rebind to Undeclared Variable

fn main() i32:
    # ERROR CE1002: rebind to undeclared variable 'count'
    count := 5

    return Result.Ok(0)

Fix: Declare with let first (let i32 count = 0) before rebinding with :=.

CE2024: Use of Destroyed Dynamic Array

fn main() i32:
    let i32[] arr = from([1, 2, 3])
    arr.destroy()

    # ERROR CE2024: use of destroyed dynamic array 'arr'
    println(arr.len())

    return Result.Ok(0)

Fix: Don't use a variable after .destroy(), or use .free() instead.

CE2502: .realise() Wrong Argument Count

fn get_value() i32:
    return Result.Ok(42)

fn main() i32:
    let Result<i32, StdError> r = get_value()

    # ERROR CE2502: realise() requires exactly 1 argument, got 0
    let i32 x = r.realise()

    return Result.Ok(0)

Fix: Provide default value: r.realise(0).

CE2503: .realise() Type Mismatch

fn get_value() i32:
    return Result.Ok(42)

fn main() i32:
    let Result<i32, StdError> r = get_value()

    # ERROR CE2503: realise() default type mismatch: expected 'i32', got 'string'
    let i32 x = r.realise("wrong")

    return Result.Ok(0)

Fix: Use correct type: r.realise(0).

CE2002: Assigning Result Without Handling

Assigning a Result-returning call directly to a non-Result variable is a type mismatch:

fn get_value() i32:
    return Result.Ok(42)

fn main() i32:
    # ERROR CE2002: type mismatch: cannot assign Result<i32, StdError> to i32
    let i32 x = get_value()

    return Result.Ok(0)

Fix: Use .realise(): let i32 x = get_value().realise(0).

CE2507: Using ?? on Non-Result Type

fn main() i32:
    let i32 x = 5

    # ERROR CE2507: ?? can only be used with Result<T> or Maybe<T>
    let i32 y = x??

    return Result.Ok(0)

Fix: Only use ?? with Result<T> or Maybe<T>.

CE2508: Using ?? Outside Result Function

extend i32 squared() i32:
    # ERROR CE2508: ?? only works in Result-returning functions
    let i32 x = might_fail()??

    return self * self

Fix: Don't use ?? in extension methods (limitation). Extension methods return their value directly (bare return, no Result.Ok(...) wrapper).

Constant Expression Errors

CE0108: Expression Not Compile-Time Constant

fn get_value() i32:
    return Result.Ok(42)

# ERROR CE0108: Expression is not a compile-time constant
const i32 X = get_value()

Fix: Only use compile-time evaluable expressions (literals, arithmetic, bitwise, etc.).

CE0109: Circular Constant Dependency

# ERROR CE0109: Circular constant dependency detected: A -> B -> A
const i32 A = B + 1
const i32 B = A + 1

Fix: Remove circular dependencies between constants.

CE0110: Unsupported Operation in Constant

# ERROR CE0110: Unsupported operation '&' in constant expression
const f64 INVALID = 3.14 & 2.0  # Bitwise AND on float

Fix: Use only supported operations for the type (bitwise only on integers).

CE0111: Invalid Type Cast in Constant

# ERROR CE0111: Invalid type cast in constant expression from string to i32
const i32 INVALID = "hello" as i32

Fix: Only cast between compatible numeric types.

CE0112: Division by Zero in Constant

# ERROR CE0112: Division by zero in constant expression
const i32 INVALID = 100 / 0

Fix: Ensure divisor is non-zero.

Warnings

CW2001: Unused Result Value

fn get_value() i32:
    return Result.Ok(42)

fn main() i32:
    # WARNING CW2001: Unused Result<i32> value
    get_value()

    return Result.Ok(0)

Fix: Handle result or explicitly discard:

let Result<i32> r = get_value()  # Store for later
let i32 x = get_value().realise(0)  # Use immediately

Runtime Errors

RE2020: Array Bounds Check Failed

fn main() i32:
    let i32[3] arr = [1, 2, 3]
    let i32 i = 10

    # Runtime error: direct indexing out of bounds
    let i32 x = arr[i]

    return Result.Ok(0)

Direct indexing (arr[i]) is checked at runtime and aborts on an out-of-bounds access. (Use arr.get(i), which returns Maybe<i32>, for safe access instead.)

Runtime output:

Runtime Error RE2020: array index 10 out of bounds for array of size 3

RE2021: Memory Allocation Failed

Runtime error RE2021: Memory allocation failed (malloc returned null)

Occurs when system runs out of memory during dynamic allocation.

Testing

Test Runner

# Run all tests (compilation only)
python tests/run_tests.py

# Run with runtime validation
python tests/run_tests.py --enhanced

# Filter tests by pattern
python tests/run_tests.py --filter hashmap
python tests/run_tests.py --filter test_result

Test Types

Positive tests (test_*.sushi): - Must compile successfully (exit code 0) - Executable may or may not run

Warning tests (test_warn_*.sushi): - Must compile with warnings (exit code 1)

Error tests (test_err_*.sushi): - Must fail compilation (exit code 2) - Used to verify error detection

Writing Tests

# tests/test_my_feature.sushi
fn main() i32:
    let i32 x = 42
    println("Test passed")
    return Result.Ok(0)
# Run your test
./sushic tests/test_my_feature.sushi
./test_my_feature

Test Naming Conventions

  • test_<feature>.sushi - Positive test
  • test_warn_<feature>.sushi - Expected warning
  • test_err_<feature>.sushi - Expected error
  • test_<category>_<specific>.sushi - Organized by category

See also: - Getting Started - Installation and first program - Language Reference - Complete syntax - Compiler Internals - How the compiler works