Compiler Architecture¶
Internal documentation for Sushi compiler architecture and design.
Overview¶
Sushi follows a clean multi-pass compiler architecture:
Source Code (.sushi)
β
Lark Parser (grammar.lark)
β
AST Builder (semantics/ast_builder/)
β
Multi-Pass Semantic Analysis (semantics/passes/) β always whole-program
β
Per-Unit Fingerprint Computation β cache check
β
LLVM IR Generation (backend/codegen_llvm.py) β per-unit, cached .o files
β
LLVM Optimization Pipeline
β
Clang Linking (all .o files)
β
Native Executable
For multi-unit projects, the compiler uses incremental compilation: each unit
is compiled to its own .o file and cached in __sushi_cache__/. Only units
whose semantic fingerprint has changed are recompiled. Single-file programs use
the direct monolithic path.
Directory Structure¶
sushi/
βββ compiler/ # Compiler package (entry point: compiler/cli.py:main)
β βββ cli.py # CLI argument parsing, main() entry point
β βββ pipeline.py # Multi-file compilation orchestration
β βββ loader.py # Unit loading and dependency resolution
β βββ cache.py # Incremental compilation cache manager
β βββ fingerprint.py # Per-unit semantic fingerprint computation
βββ grammar.lark # Lark grammar specification
βββ semantics/
β βββ ast_builder/ # Modular AST construction, split by concern
β β βββ builder.py # Main orchestrator
β β βββ declarations/ # Top-level constructs
β β β βββ toplevel.py # One table sorts a unit's declarations
β β β βββ functions.py # Function parsing
β β β βββ structs.py # Struct definitions
β β β βββ enums.py # Enum definitions
β β β βββ extensions.py # Extension methods
β β β βββ perks.py # Perk definitions
β β β βββ constants.py # Constant declarations
β β β βββ imports.py # Use statements
β β βββ expressions/ # Expression parsing
β β β βββ parser.py # Main expression parser
β β β βββ literals.py # Literal values
β β β βββ operators.py # Binary/unary operators
β β β βββ calls.py # Function calls
β β β βββ members.py # Member access
β β β βββ arrays.py # Array expressions
β β β βββ chains.py # Chained expressions
β β βββ statements/ # Statement parsing
β β β βββ parser.py # Main statement parser
β β β βββ variables.py # Let/rebind
β β β βββ returns.py # Return statements
β β β βββ control_flow.py # If/elif/else
β β β βββ loops.py # While/foreach
β β β βββ matching.py # Pattern matching
β β β βββ blocks.py # Block statements
β β β βββ flow.py # Break/continue
β β β βββ io.py # Print/println
β β β βββ calls.py # Statement-level calls
β β βββ types/ # Type parsing
β β β βββ parser.py # Main type parser
β β β βββ generics.py # Generic types
β β β βββ arrays.py # Array types
β β β βββ references.py # Reference types
β β β βββ user_defined.py # Struct/enum types
β β βββ utils/ # Shared utilities
β β β βββ tree_navigation.py # Tree traversal
β β β βββ expression_discovery.py # Expression finding
β β β βββ string_processing.py # String handling
β β βββ exceptions.py # Custom parsing exceptions
β βββ passes/
β β βββ collect/ # the collect pass
β β β βββ constants.py # Constant definitions
β β β βββ functions.py # Function signatures
β β β βββ structs.py # Struct definitions
β β β βββ enums.py # Enum definitions
β β β βββ perks.py # Perk definitions
β β β βββ utils.py # Collection utilities
β β βββ scope.py # the scope pass
β β βββ resolve.py # the resolve pass
β β βββ const_eval.py # constant evaluation (a helper, NOT a pass)
β β βββ derive.py # the derive pass (hash + clone)
β β βββ finite_types.py # the finite-types pass
β β βββ lift.py # the lift pass (lambda lifting)
β β βββ borrow/ # the borrow pass (state in __init__.py,
β β β β # free-function siblings: statements, expressions,
β β β β # borrows, bindings, calls, consume, reads, writes,
β β β β # flow, diagnostics, types, destroy_effects)
β β βββ types/ # the typecheck pass, modular
β β βββ utils.py # Type utilities
β β βββ inference.py # Type inference
β β βββ compatibility.py # Type compatibility
β β βββ propagation.py # Type propagation
β β βββ resolution.py # Type resolution
β β βββ result_validation.py # Result handling
β β βββ field_matcher.py # Struct field matching
β β βββ perks.py # Perk constraint checking
β β βββ expressions.py # Expression type checking
β β βββ matching.py # Pattern match validation
β β βββ statements.py # Statement validation
β β βββ calls/ # Function call validation
β β βββ user_defined.py # User function calls
β β βββ methods.py # Method calls
β β βββ structs.py # Struct construction
β β βββ enums.py # Enum construction
β β βββ generics.py # Generic calls
β βββ generics/
β βββ types.py # Generic type definitions
β βββ name_mangling.py # Name mangling for monomorphization
β βββ constraints.py # Perk constraints
β βββ instantiate/ # the instantiate pass
β β βββ types.py # Type instantiations
β β βββ functions.py # Function instantiations
β β βββ expressions.py # Expression instantiations
β βββ monomorphize/ # the monomorphize pass
β β βββ transformer.py # Main transformer
β β βββ types.py # Type monomorphization
β β βββ functions.py # Function monomorphization
β βββ providers/ # Generic type providers
β βββ interface.py # Provider protocol
β βββ registry.py # Provider registry
βββ backend/
β βββ codegen_llvm.py # Main LLVM orchestrator
β βββ constants/ # Centralized LLVM constants (DRY)
β β βββ llvm_values.py # FALSE_I1, ZERO_I32, make_i32_const(), etc
β β βββ bit_widths.py
β β βββ error_codes.py
β β βββ hash_constants.py
β β βββ indices.py
β β βββ sizes.py
β βββ gep_utils.py # GetElementPtr utilities
β βββ enum_utils.py # Enum tag/data utilities
β βββ destructors.py # Unified recursive destruction
β βββ platform_detect.py # Target platform detection
β βββ expressions/ # Expression emission (array codegen lives under
β β β # backend/types/arrays/ instead, see below)
β β βββ literals.py
β β βββ operators.py # Binary/unary operators
β β βββ memory.py
β β βββ casts.py
β β βββ structs.py # Struct operations
β β βββ enums.py
β β βββ type_utils.py
β β βββ calls/ # Subdivided for complexity
β β βββ dispatcher.py # Main call routing
β β βββ generics.py # Generic method instantiation
β β βββ intrinsics.py # Compiler intrinsics
β β βββ file_open.py # File I/O operations
β β βββ utils.py # Call utilities
β β βββ stdlib/ # Standard library calls, one module per area
β β # (io.py, strings.py, math.py, time.py, random.py, env.py,
β β # process.py, primitives.py)
β βββ statements/ # Statement emission
β β βββ io.py
β β βββ loops.py # while, foreach (19KB)
β β βββ control_flow.py
β β βββ returns.py
β β βββ variables.py # let, rebind (13KB)
β β βββ matching.py # Pattern matching (20KB)
β β βββ initialization.py # Variable initialization patterns
β β βββ utils.py # Statement utilities (11KB)
β βββ runtime/ # Runtime support
β β βββ strings.py # String operations
β β βββ formatting.py # String interpolation
β β βββ errors.py # Error handling
β β βββ externs/ # Organized libc bindings
β β βββ libc_stdio.py # printf, fopen, etc.
β β βββ libc_strings.py # strlen, strcmp, memcpy
β β βββ libc_ctype.py # isalpha, isdigit, etc.
β β βββ libc_process.py # exit, getenv, setenv
β βββ types/ # Type-specific codegen
β β βββ arrays/ # Construction, indexing, bounds checks
β β β βββ dispatcher.py # Array-method call routing
β β β βββ indexing.py # Direct indexing, GEP helpers
β β β βββ bounds.py # Bounds-check emission
β β β βββ literals.py # Array literal construction
β β β βββ utils.py
β β β βββ methods/ # Array method implementations
β β β βββ core.py # len, get, push, pop
β β β βββ hashing.py # Hash function generation
β β β βββ iterators.py # Iterator creation
β β β βββ safe_access.py # .get() Maybe@(T) wrapping
β β β βββ transforms.py # fill, reverse, clone
β β β βββ utf8_validate.py # u8[].to_string_checked()
β β βββ primitives/ # i8..u64, f32/f64, bool
β β β βββ bit_reinterpret.py # to_bits()/from_bits()
β β β βββ hashing.py
β β β βββ to_str.py
β β βββ structs.py
β β βββ enums.py
β β βββ hash_utils.py
β βββ memory/ # Memory management
β β βββ scopes.py # Scope-based cleanup
β β βββ dynamic_arrays.py # Dynamic array management
β β βββ heap.py # Heap allocation (malloc/free)
β βββ generics/ # Generic type implementations
β βββ codegen.py # Generic code generation
β βββ enum_methods_base.py # Base for Result/Maybe
β βββ extensions.py # Generic extension methods
β βββ maybe.py # Maybe@(T) (19KB)
β βββ own.py # Own@(T)
β βββ results.py # Result@(T)
β βββ hashmap/ # HashMap@(K,V) implementation
β β βββ types.py
β β βββ validation.py
β β βββ utils.py
β β βββ methods/
β β βββ core.py # new, insert, get, contains
β β βββ mutations.py # remove, free, rehash
β β βββ debug.py # debug printing
β β βββ iterators.py # iterator support
β βββ list/ # List@(T) implementation
β βββ types.py
β βββ validation.py
β βββ methods_simple.py # len, is_empty
β βββ methods_capacity.py # reserve, shrink
β βββ methods_modify.py # push, insert, remove
β βββ methods_destroy.py # free, destroy
β βββ methods_debug.py # debug printing
β βββ methods_iter.py # iterator support
βββ stdlib/
βββ src/ # Python source (LLVM IR generators)
β βββ common.py # Shared utilities
β βββ conversions.py # Type conversions
β βββ error_emission.py # Error code helpers
β βββ ir_builders.py # IR construction helpers
β βββ ir_common.py # Common IR patterns
β βββ libc_declarations.py # Centralized libc declarations
β βββ string_helpers.py # String operation helpers
β βββ type_converters.py # Type conversion utilities
β βββ type_definitions.py # Type definition helpers
β βββ collections/
β β βββ strings/ # String operations (organized)
β β β βββ common.py
β β β βββ compiler/ # Built-in string ops
β β β βββ intrinsics/ # Low-level UTF-8 ops
β β β βββ methods/ # High-level methods
β β βββ list.py # List@(T)
β β βββ hashmap.py # HashMap@(K,V)
β βββ io/
β β βββ stdio/ # Platform-specific stdio
β β β βββ common.py
β β β βββ darwin.py
β β β βββ linux.py
β β βββ files/ # File operations
β β βββ common.py
β β βββ read.py
β β βββ write.py
β β βββ seek.py
β β βββ status.py
β β βββ iterators.py
β β βββ binary.py
β βββ math/ # Math operations
β β βββ operations.py
β βββ time/ # Time/sleep functions
β βββ random/ # Random number generation
β β βββ generators.py
β βββ sys/ # System modules
β β βββ env/ # Environment variables
β β βββ process/ # Process control
β βββ _platform/ # Platform-specific implementations
β βββ __init__.py # get_platform_module() helper
β βββ posix/ # POSIX implementations
β βββ darwin/ # macOS implementations
β βββ linux/ # Linux implementations
βββ dist/ # Platform-organized precompiled .bc files
βββ darwin/ # macOS
β βββ collections/strings.bc
β βββ io/files.bc
β βββ math.bc
β βββ time.bc
β βββ random.bc
β βββ sys/
β βββ env.bc
β βββ process.bc
βββ linux/ # Linux (similar structure)
Semantic Passes¶
Fifteen passes, in this order. The passes have NAMES, not numbers -- a number goes out of
order the moment a pass is inserted between two others. SemanticAnalyzer.check() is the
code authority; semantic-passes.md documents each pass in detail.
whole program, once:
collect -> externs -> libraries -> entrypoint -> instantiate -> monomorphize
-> resolve -> finite-types -> derive -> shadowing -> effects
then per unit, in one loop:
scope -> typecheck -> lift -> borrow
Six of them have no section below: externs (FFI signature validation), libraries
(library symbol registration), entrypoint (main()'s signature), shadowing (an
extension may not shadow a built-in, CE2097), effects (the destroy-effect summary) and
lift (each lambda becomes a top-level function plus an environment).
semantic-passes.md covers all six.
collect: headers and constants¶
Files: semantics/passes/collect/*.py
Responsibilities:
- Parse constant definitions (collect/constants.py)
- Collect function signatures (collect/functions.py)
- Register struct definitions (collect/structs.py)
- Register enum definitions (collect/enums.py)
- Register perk definitions (collect/perks.py)
- Build initial symbol table
Output: - Global constants map - Function signature registry - Generic type definitions
instantiate: generic instantiation collection¶
Files: semantics/generics/instantiate/*.py
Responsibilities:
- Detect generic type usage (instantiate/types.py)
- Detect generic method calls (instantiate/functions.py)
- Infer type arguments from usage (instantiate/expressions.py)
- Collect all required instantiations
Example:
let List@(i32) nums = List.new() # Collect: List@(i32)
nums.push(42) # Collect: List@(i32).push
monomorphize: generic to concrete¶
Files: semantics/generics/monomorphize/*.py
Responsibilities:
- Substitute generic type parameters (monomorphize/transformer.py)
- Create concrete types from generic definitions (monomorphize/types.py)
- Generate specialized function/method instances (monomorphize/functions.py)
Example:
extend Box@(T) unwrap() T
β
extend Box@(i32) unwrap() i32
extend Box@(string) unwrap() string
resolve: field and variant type resolution¶
File: semantics/passes/resolve.py
Responsibilities: - Resolve every struct field type to the interned type the tables hold - Resolve every enum variant's associated types the same way
Example:
struct Rectangle:
Point top_left # collected as UnknownType("Point")
# β
# resolved to the interned StructType
Type identity is nominal, so this pass is what makes the table entry the one authority
(docs/design/type-identity.md).
derive: hash and clone auto-derivation¶
File: semantics/passes/derive.py
Responsibilities:
- Auto-generate .hash() and .clone() for all types
- Compose hash functions for structs
- Validate hashability
Derived for: - Primitives (FxHash for ints, FNV-1a for strings) - Structs (field-wise hashing) - Enums (discriminant + variant data hashing) - Arrays (element-wise hashing)
scope: scope and variables¶
File: semantics/passes/scope.py
Responsibilities: - Variable declaration and usage tracking - Scope analysis - Move semantics validation - Borrow tracking initialization
Output: - Variable scopes - Move analysis results - Borrow tracking data
typecheck: type validation¶
Files: semantics/passes/types/*.py
Responsibilities: - Type checking all expressions - Result@(T) handling validation - Pattern match exhaustiveness - Type compatibility checking
Modular type checking:
- types/resolution.py - Type resolution (Result@(T) wrapping)
- types/propagation.py - Type propagation to constructors
- types/result_validation.py - Result.Ok/Err validation
- types/expressions.py - Expression type checking
- types/statements.py - Statement validation
- types/calls/*.py - Function call validation (user-defined, methods, structs, enums, generics)
- types/matching.py - Pattern match validation
- types/compatibility.py - Type compatibility checking
- types/inference.py - Type inference
- types/perks.py - Perk constraint checking
- types/field_matcher.py - Struct field matching
borrow: borrow checking¶
File: semantics/passes/borrow/
__init__.py-BorrowChecker: the state,run(), and one callable's setupstatements.py/expressions.py- the two walks, each amatchover the nodeborrows.py/bindings.py/calls.py/consume.py- the rulesreads.py/types.py- what an expression reads, and the type algebra behind itwrites.py- theREADONLY_RECEIVERSgate tableflow.py/state.py/diagnostics.py/destroy_effects.py
Responsibilities: - Ensure single active borrow per variable - Prevent move-while-borrowed - Prevent use-after-destroy - Track reference lifetimes
Errors detected: - CE2405: Use of moved variable - CE1007: Cannot rebind while borrowed - CE2406: Use of destroyed variable
Incremental Compilation¶
Multi-unit projects use per-unit .o file caching to avoid redundant LLVM codegen.
Architecture¶
Parse all .sushi files (always)
β Whole-program semantic analysis (always, fast Python)
β Compute per-unit semantic fingerprints (always, fast)
β Per-unit LLVM codegen β .o file (only if fingerprint changed)
β Link all .o files β executable (always)
Semantic analysis (passes 0-3) remains whole-program because generic instantiation collection and monomorphization need the complete call graph. This is pure Python and runs in well under a second. The expensive part β LLVM codegen + optimization + object emission β is cached per-unit.
Cache Structure¶
__sushi_cache__/
cache.json β manifest (compiler version, platform, opt level)
units/
main.o β cached object file
main.o.fingerprint β semantic fingerprint
helpers/math.o
helpers/math.o.fingerprint
lib/mylib/mylib.o β a SOURCE library's unit, cached like any other
stdlib/
io_stdio.o β compiled stdlib bitcode
libsrc/
mylib/mylib.sushi β a source library's materialized units
libs/
mylib.o β a BINARY library's bitcode, compiled once
A source library has no separate object of its own: its units are ordinary units, so they
land under units/ (namespaced lib/<library>/) and are invalidated by the same fingerprint
machinery as the consumer's own files.
Fingerprint Computation¶
Each unit's fingerprint is a SHA-256 hash of: - Source file content - Public symbol signatures from dependencies - AST structure (structs, enums, extensions, perk impls, use statements) - Monomorphized extensions consumed by this unit
Linkage Rules¶
- Public functions/constants:
externallinkage - Private functions/constants:
internallinkage - Monomorphized generics:
linkonce_odrlinkage (linker deduplicates across units) - Inline runtime functions (
llvm_strlen,llvm_strcmp,utf8_char_count):linkonce_odr
Key Files¶
compiler/pipeline.pyβ orchestrates monolithic vs incremental compilation pathscompiler/cache.pyβCacheManagerclass: directory management, manifest, staleness detectioncompiler/fingerprint.pyβcompute_unit_fingerprint(),compute_stdlib_fingerprint(),compute_lib_fingerprint()backend/codegen_llvm.pyβbuild_module_single_unit(),compile_single_unit_to_object(),link_object_files()
Backend Architecture¶
LLVM Code Generation¶
Main file: backend/codegen_llvm.py
Process: 1. Create LLVM module and function declarations 2. Emit standard library linkage 3. Generate code for each function 4. Apply optimization passes 5. Link with clang
Key classes:
- LLVMCodeGenerator - Main orchestrator
- ExpressionEmitter - Expression code generation
- StatementEmitter - Statement code generation
- TypeManager - LLVM type creation
- MemoryManager - RAII and cleanup
Expression Emission¶
Located in backend/expressions/:
- literals.py - Constants, strings, arrays
- operators.py - Binary/unary operations
- memory.py - Loads, stores, references
- casts.py - Type conversions
- structs.py - Struct field access
- enums.py - Enum construction/matching
- calls/ - Function/method calls, subdivided by concern:
dispatcher.py(main call routing),generics.py,intrinsics.py,file_open.py,utils.py, and astdlib/subpackage with one module per stdlib area
Array expression codegen (construction, indexing, methods) lives under
backend/types/arrays/ instead β see Type System below.
Statement Emission¶
Located in backend/statements/:
- io.py - println, print, stdin/stdout
- loops.py - while, foreach
- control_flow.py - if/elif/else, break, continue
- returns.py - Result.Ok/Err returns
- variables.py - let, rebind
- matching.py - match expressions
Type System¶
Located in backend/types/:
- primitives/ - i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool
(
bit_reinterpret.py,hashing.py,to_str.py) - arrays/ - Fixed and dynamic arrays: construction, indexing, bounds checks,
and a
methods/subpackage for.len()/.get()/.push()/etc - structs.py - Struct layout and field access
- enums.py - Enum discriminant and variant data
- hash_utils.py - Hash function generation
Runtime Support¶
Located in backend/runtime/:
- strings.py - String operations (len, size, find, split, trim)
- formatting.py - String interpolation
- errors.py - Runtime error messages
- libc_externs.py - malloc, free, printf, etc.
Standard Library¶
Structure¶
stdlib/
βββ src/ # Python code that generates LLVM IR
βββ dist/ # Precompiled .bc files
Import Mechanism¶
use <collections/strings>
Maps to: stdlib/dist/collections/strings.bc
Building stdlib¶
# Rebuild stdlib modules
cd stdlib/src/collections
python strings.py # Generates ../../dist/collections/strings.bc
Optimization Pipeline¶
Levels¶
- none - No optimization
- mem2reg - SROA only (default)
- O1 - Basic (CFG simplification, DCE, instruction combining)
- O2 - Moderate (+ loop opts, GVN, SCCP, jump threading)
- O3 - Aggressive (+ loop unrolling, strength reduction, inlining)
LLVM Passes¶
Applied in backend/codegen_llvm.py:apply_optimizations():
pm = llvm.ModulePassManager()
if opt_level == 'O1':
pm.add_promote_memory_to_register_pass()
pm.add_cfg_simplification_pass()
pm.add_instruction_combining_pass()
pm.add_dead_code_elimination_pass()
elif opt_level == 'O2':
# O1 passes + ...
pm.add_sccp_pass()
pm.add_loop_rotation_pass()
pm.add_gvn_pass()
# ... etc
pm.run(module)
Recent Architectural Improvements¶
Breaking Circular Imports: Deferred Local Imports¶
There used to be a backend/interfaces.py module of Protocol classes for this
purpose; it was deleted (Tier 4.5) and no Protocol-based scheme has replaced it
(grep -rl Protocol sushi_lang/backend now returns nothing). The mechanism that
actually breaks circular dependencies between backend components today is simpler:
an import that would cycle at module-load time is written inside the function
or method that needs it instead of at the top of the file, so the cycle only
has to resolve at call time, by which point both modules have finished loading.
Example (backend/destructors.py, _emit_list_value_destructor):
def _emit_list_value_destructor(codegen, builder, value_ptr, value_type):
from sushi_lang.backend.generics.list.types import extract_element_type
element_type = extract_element_type(value_type, codegen)
...
generics/list/types.py needs destructor helpers, and destructors.py needs to
inspect a List@(T)'s element type β a top-level import on either side would
cycle. Deferring the import into the function body sidesteps it.
A second, narrower use of TYPE_CHECKING-guarded imports covers pure type
annotations (no runtime import at all), e.g. backend/enum_utils.py:
if TYPE_CHECKING:
from sushi_lang.backend.codegen_llvm import LLVMCodegen
Benefits (unchanged from the old Protocol approach): - Eliminates circular import issues - Reduces coupling between modules β a module only pays the import cost for the functions it actually calls
Centralized Utilities (DRY Principle)¶
Recent refactors have extracted common patterns into reusable utility modules:
LLVM Constants Module¶
File: backend/constants/llvm_values.py
Eliminates duplication of LLVM constant creation across 100+ call sites.
Provides:
# Boolean constants
FALSE_I1 = ir.Constant(ir.IntType(1), 0)
TRUE_I1 = ir.Constant(ir.IntType(1), 1)
# Integer constants
ZERO_I8 = ir.Constant(ir.IntType(8), 0)
ONE_I8 = ir.Constant(ir.IntType(8), 1)
ZERO_I32 = ir.Constant(ir.IntType(32), 0)
ONE_I32 = ir.Constant(ir.IntType(32), 1)
TWO_I32 = ir.Constant(ir.IntType(32), 2)
ZERO_I64 = ir.Constant(ir.IntType(64), 0)
ONE_I64 = ir.Constant(ir.IntType(64), 1)
# Factory functions
def make_i8_const(value: int) -> ir.Constant: ...
def make_i32_const(value: int) -> ir.Constant: ...
def make_i64_const(value: int) -> ir.Constant: ...
GetElementPtr Utilities¶
File: backend/gep_utils.py
Type-safe helpers for GEP (GetElementPtr) operations.
Functions:
def gep_struct_field(builder, struct_ptr, field_index):
"""Access struct field by index"""
return builder.gep(struct_ptr, [ZERO_I32, make_i32_const(field_index)])
def gep_array_element(builder, array_ptr, index):
"""Access array element"""
return builder.gep(array_ptr, [ZERO_I32, index])
def gep_dynamic_array_data(builder, dynarray_ptr):
"""Get data pointer from dynamic array struct"""
return builder.gep(dynarray_ptr, [ZERO_I32, ZERO_I32])
def gep_dynamic_array_len(builder, dynarray_ptr):
"""Get length from dynamic array struct"""
return builder.gep(dynarray_ptr, [ZERO_I32, ONE_I32])
def gep_dynamic_array_capacity(builder, dynarray_ptr):
"""Get capacity from dynamic array struct"""
return builder.gep(dynarray_ptr, [ZERO_I32, TWO_I32])
Enum Utilities¶
File: backend/enum_utils.py
Centralized enum discriminant and variant data operations.
Functions:
def extract_enum_tag(builder, enum_ptr):
"""Load discriminant tag from enum"""
tag_ptr = gep_struct_field(builder, enum_ptr, 0)
return builder.load(tag_ptr)
def extract_enum_data(builder, enum_ptr, variant_type):
"""Extract variant data from enum"""
data_ptr = gep_struct_field(builder, enum_ptr, 1)
typed_ptr = builder.bitcast(data_ptr, variant_type.as_pointer())
return builder.load(typed_ptr)
def compare_enum_variant(builder, enum_ptr, expected_tag):
"""Check if enum matches variant tag"""
actual_tag = extract_enum_tag(builder, enum_ptr)
return builder.icmp_signed('==', actual_tag, make_i32_const(expected_tag))
Unified Destructor Logic¶
File: backend/destructors.py
Replaces scattered destruction code with single recursive implementation.
Function:
def emit_value_destructor(codegen, builder, value, llvm_type, ast_type):
"""
Recursively destroy value based on type.
Handles:
- Primitives: no-op
- Strings: no-op (immutable)
- Dynamic arrays: destroy elements, free buffer
- Structs: destroy each field recursively
- Enums: switch on discriminant, destroy variant data
- Own@(T): destroy owned value, free pointer
"""
# Type-aware dispatch...
Benefits: - Eliminates duplicated destruction logic (previously in 10+ files) - Ensures consistent cleanup behavior - Single point of maintenance for RAII - Powers HashMap.free(), List.destroy(), and error propagation cleanup
PassErrorReporter Helper¶
File: semantics/error_reporter.py
Reduces boilerplate in semantic passes by binding Reporter instance.
Before:
def validate_expression(expr, reporter):
if not is_valid(expr):
reporter.error("CE2001", f"Invalid expression: {expr}")
# reporter passed to every function...
After:
error_reporter = PassErrorReporter(reporter)
def validate_expression(expr):
if not is_valid(expr):
error_reporter.error("CE2001", f"Invalid expression: {expr}")
# No need to pass reporter around
Modularized Backend Structure¶
The backend has been organized into logical subdirectories for better maintainability:
backend/expressions/calls/- Subdivided due to complexity; stdlib call routing is itself astdlib/subpackage, one module per stdlib areabackend/generics/- Complete generic type system with HashMap and List implementationsbackend/memory/- Separated scope management, dynamic arrays, and heap operationsbackend/runtime/externs/- Organized libc bindings by category (stdio, strings, ctype, process)backend/types/arrays/methods/- Array method implementations organized by category
Key Design Patterns¶
Deferred Local Imports¶
There is no shared protocol/interface module. Circular dependencies between
backend components are avoided by importing inside the function that needs the
dependency rather than at module top level β see
Breaking Circular Imports: Deferred Local Imports
above for the mechanism and a concrete example from backend/destructors.py.
Recursive Destructors¶
MemoryManager.emit_value_destructor() handles cleanup for all types:
- Primitives: no-op
- Strings: no-op (immutable)
- Arrays: iterate and destroy elements, free buffer
- Structs: destroy each field, free struct
- Enums: switch on discriminant, destroy variant data
- Own@(T): destroy owned value, free pointer
Move Tracking¶
Variables marked as moved:
self.moved_variables.add(var_name)
Checked on every use:
if var_name in self.moved_variables:
raise CompilerError(f"CE2405: Use of moved variable '{var_name}'")
Borrow Tracking¶
Active borrows tracked per variable:
self.active_borrows[var_name] = borrow_id
Prevents rebinding while borrowed:
if var_name in self.active_borrows:
raise CompilerError(f"CE1007: Cannot rebind '{var_name}' while borrowed")
Error Handling¶
Error Code Ranges¶
- CE0xxx - Internal errors (function-related)
- CE1xxx - Scope/variable errors (undefined, moved, borrowed)
- CE2xxx - Type errors (incompatible types, array bounds)
- CE3xxx - Unit errors (module system)
- CWxxxx - Warnings (unused Result, etc.)
- RExxxx - Runtime errors (bounds check, malloc failure)
Error Reporting¶
Located in each pass file. Example:
raise CompilerError(
f"CE2505: Cannot assign Result@({inner}) to {target_type} without handling. "
f"Use .realise(default) to unwrap the Result."
)
Testing Strategy¶
Test Types¶
test_*.sushi- Must compile (exit 0)test_warn_*.sushi- Compile with warnings (exit 1)test_err_*.sushi- Must fail (exit 2)
Test Runner¶
python tests/run_tests.py
Compiles all tests and verifies expected exit codes.
Development Workflow¶
Adding a New Feature¶
- Update grammar (
grammar.lark) - Update AST builder (
semantics/ast_builder/β pick the matching module underdeclarations/,expressions/,statements/, ortypes/) - Add semantic analysis (appropriate phase)
- Add code generation (
backend/) - Write tests (
tests/test_feature.sushi) - Run test suite
Debugging Compiler Issues¶
# See full traceback
./sushic --traceback program.sushi
# View AST
./sushic --dump-ast program.sushi
# View generated IR
./sushic --dump-ll program.sushi
# Save IR to file
./sushic --write-ll program.sushi
See also: - Semantic Passes - Detailed pass-by-pass analysis - Backend - LLVM code generation details