Skip to content

Backends API

Backend base classes and implementations.

Base Classes

LanguageBackend

multigen.backends.base.LanguageBackend

Bases: ABC

Abstract base for all language backends in MultiGen.

Source code in src/multigen/backends/base.py
class LanguageBackend(ABC):
    """Abstract base for all language backends in MultiGen."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize backend with optional preferences."""
        self.preferences = preferences

    @abstractmethod
    def get_name(self) -> str:
        """Language name (e.g., 'rust', 'go', 'cpp', 'c')."""

    @abstractmethod
    def get_file_extension(self) -> str:
        """Source file extension (e.g., '.rs', '.go', '.cpp', '.c')."""

    @abstractmethod
    def get_factory(self) -> "AbstractFactory":
        """Get language-specific code element factory."""

    @abstractmethod
    def get_emitter(self) -> "AbstractEmitter":
        """Get language-specific code emitter."""

    @abstractmethod
    def get_builder(self) -> "AbstractBuilder":
        """Get language-specific build system."""

    @abstractmethod
    def get_container_system(self) -> "AbstractContainerSystem":
        """Get language-specific container library integration."""

    def get_optimizer(self) -> Optional["AbstractOptimizer"]:
        """Get language-specific optimizer (optional).

        Backends with optimization support should override this method
        to return their optimizer instance. The optimizer implements
        the AbstractOptimizer interface for standardized optimization.

        Returns:
            AbstractOptimizer instance if the backend supports optimization,
            None otherwise (default).
        """
        return None

__init__(preferences=None)

Initialize backend with optional preferences.

Source code in src/multigen/backends/base.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize backend with optional preferences."""
    self.preferences = preferences

get_builder() abstractmethod

Get language-specific build system.

Source code in src/multigen/backends/base.py
@abstractmethod
def get_builder(self) -> "AbstractBuilder":
    """Get language-specific build system."""

get_container_system() abstractmethod

Get language-specific container library integration.

Source code in src/multigen/backends/base.py
@abstractmethod
def get_container_system(self) -> "AbstractContainerSystem":
    """Get language-specific container library integration."""

get_emitter() abstractmethod

Get language-specific code emitter.

Source code in src/multigen/backends/base.py
@abstractmethod
def get_emitter(self) -> "AbstractEmitter":
    """Get language-specific code emitter."""

get_factory() abstractmethod

Get language-specific code element factory.

Source code in src/multigen/backends/base.py
@abstractmethod
def get_factory(self) -> "AbstractFactory":
    """Get language-specific code element factory."""

get_file_extension() abstractmethod

Source file extension (e.g., '.rs', '.go', '.cpp', '.c').

Source code in src/multigen/backends/base.py
@abstractmethod
def get_file_extension(self) -> str:
    """Source file extension (e.g., '.rs', '.go', '.cpp', '.c')."""

get_name() abstractmethod

Language name (e.g., 'rust', 'go', 'cpp', 'c').

Source code in src/multigen/backends/base.py
@abstractmethod
def get_name(self) -> str:
    """Language name (e.g., 'rust', 'go', 'cpp', 'c')."""

get_optimizer()

Get language-specific optimizer (optional).

Backends with optimization support should override this method to return their optimizer instance. The optimizer implements the AbstractOptimizer interface for standardized optimization.

Returns:

Type Description
Optional[AbstractOptimizer]

AbstractOptimizer instance if the backend supports optimization,

Optional[AbstractOptimizer]

None otherwise (default).

Source code in src/multigen/backends/base.py
def get_optimizer(self) -> Optional["AbstractOptimizer"]:
    """Get language-specific optimizer (optional).

    Backends with optimization support should override this method
    to return their optimizer instance. The optimizer implements
    the AbstractOptimizer interface for standardized optimization.

    Returns:
        AbstractOptimizer instance if the backend supports optimization,
        None otherwise (default).
    """
    return None

AbstractEmitter

multigen.backends.base.AbstractEmitter

Bases: ABC

Abstract emitter for generating language-specific code.

Source code in src/multigen/backends/base.py
class AbstractEmitter(ABC):
    """Abstract emitter for generating language-specific code."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize emitter with optional preferences."""
        self.preferences = preferences

    @abstractmethod
    def emit_function(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> str:
        """Generate complete function in target language."""

    @abstractmethod
    def emit_module(self, source_code: str, analysis_result: Any, semantic_mapping: Any = None) -> str:
        """Generate complete module/file in target language.

        Args:
            source_code: Original Python source code
            analysis_result: Analysis phase result with AST info
            semantic_mapping: Optional SemanticMapping from Phase 4 with pre-computed
                             type mappings, container mappings, and function return types.
                             If provided, emitters can use these instead of re-computing.
        """

    @abstractmethod
    def map_python_type(self, python_type: str) -> str:
        """Map Python type to target language type."""

    @abstractmethod
    def can_use_simple_emission(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> bool:
        """Determine if function can use simple emission strategy."""

__init__(preferences=None)

Initialize emitter with optional preferences.

Source code in src/multigen/backends/base.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize emitter with optional preferences."""
    self.preferences = preferences

can_use_simple_emission(func_node, type_context) abstractmethod

Determine if function can use simple emission strategy.

Source code in src/multigen/backends/base.py
@abstractmethod
def can_use_simple_emission(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> bool:
    """Determine if function can use simple emission strategy."""

emit_function(func_node, type_context) abstractmethod

Generate complete function in target language.

Source code in src/multigen/backends/base.py
@abstractmethod
def emit_function(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> str:
    """Generate complete function in target language."""

emit_module(source_code, analysis_result, semantic_mapping=None) abstractmethod

Generate complete module/file in target language.

Parameters:

Name Type Description Default
source_code str

Original Python source code

required
analysis_result Any

Analysis phase result with AST info

required
semantic_mapping Any

Optional SemanticMapping from Phase 4 with pre-computed type mappings, container mappings, and function return types. If provided, emitters can use these instead of re-computing.

None
Source code in src/multigen/backends/base.py
@abstractmethod
def emit_module(self, source_code: str, analysis_result: Any, semantic_mapping: Any = None) -> str:
    """Generate complete module/file in target language.

    Args:
        source_code: Original Python source code
        analysis_result: Analysis phase result with AST info
        semantic_mapping: Optional SemanticMapping from Phase 4 with pre-computed
                         type mappings, container mappings, and function return types.
                         If provided, emitters can use these instead of re-computing.
    """

map_python_type(python_type) abstractmethod

Map Python type to target language type.

Source code in src/multigen/backends/base.py
@abstractmethod
def map_python_type(self, python_type: str) -> str:
    """Map Python type to target language type."""

AbstractFactory

multigen.backends.base.AbstractFactory

Bases: ABC

Abstract factory for creating language-specific code elements.

Source code in src/multigen/backends/base.py
class AbstractFactory(ABC):
    """Abstract factory for creating language-specific code elements."""

    @abstractmethod
    def create_variable(self, name: str, type_name: str, value: Optional[str] = None) -> str:
        """Create variable declaration."""

    @abstractmethod
    def create_function_signature(self, name: str, params: list[tuple[str, str]], return_type: str) -> str:
        """Create function signature."""

    @abstractmethod
    def create_comment(self, text: str) -> str:
        """Create language-appropriate comment."""

    @abstractmethod
    def create_include(self, library: str) -> str:
        """Create import/include statement."""

create_comment(text) abstractmethod

Create language-appropriate comment.

Source code in src/multigen/backends/base.py
@abstractmethod
def create_comment(self, text: str) -> str:
    """Create language-appropriate comment."""

create_function_signature(name, params, return_type) abstractmethod

Create function signature.

Source code in src/multigen/backends/base.py
@abstractmethod
def create_function_signature(self, name: str, params: list[tuple[str, str]], return_type: str) -> str:
    """Create function signature."""

create_include(library) abstractmethod

Create import/include statement.

Source code in src/multigen/backends/base.py
@abstractmethod
def create_include(self, library: str) -> str:
    """Create import/include statement."""

create_variable(name, type_name, value=None) abstractmethod

Create variable declaration.

Source code in src/multigen/backends/base.py
@abstractmethod
def create_variable(self, name: str, type_name: str, value: Optional[str] = None) -> str:
    """Create variable declaration."""

Backend Modules

C Backend

multigen.backends.c

C backend for MultiGen - Clean C code generation.

CBackend

Bases: LanguageBackend

Clean C backend implementation for MultiGen.

Source code in src/multigen/backends/c/backend.py
class CBackend(LanguageBackend):
    """Clean C backend implementation for MultiGen."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize C backend with preferences."""
        if preferences is None:
            preferences = CPreferences()
        super().__init__(preferences)
        self._optimizer: Optional[NoOpOptimizer] = None

    def get_name(self) -> str:
        """Return backend name."""
        return "c"

    def get_file_extension(self) -> str:
        """Return C source file extension."""
        return ".c"

    def get_factory(self) -> AbstractFactory:
        """Get C code element factory."""
        return CFactory()

    def get_emitter(self) -> AbstractEmitter:
        """Get C code emitter."""
        return CEmitter(self.preferences)

    def get_builder(self) -> AbstractBuilder:
        """Get C build system."""
        return CBuilder()

    def get_container_system(self) -> AbstractContainerSystem:
        """Get C container system."""
        return CContainerSystem()

    def get_optimizer(self) -> AbstractOptimizer:
        """Get C optimizer (delegates to compiler).

        C optimization is handled by the compiler (gcc -O2, clang -O2, etc.)
        rather than at the code generation level.

        Returns:
            NoOpOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = NoOpOptimizer()
        return self._optimizer

__init__(preferences=None)

Initialize C backend with preferences.

Source code in src/multigen/backends/c/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize C backend with preferences."""
    if preferences is None:
        preferences = CPreferences()
    super().__init__(preferences)
    self._optimizer: Optional[NoOpOptimizer] = None

get_builder()

Get C build system.

Source code in src/multigen/backends/c/backend.py
def get_builder(self) -> AbstractBuilder:
    """Get C build system."""
    return CBuilder()

get_container_system()

Get C container system.

Source code in src/multigen/backends/c/backend.py
def get_container_system(self) -> AbstractContainerSystem:
    """Get C container system."""
    return CContainerSystem()

get_emitter()

Get C code emitter.

Source code in src/multigen/backends/c/backend.py
def get_emitter(self) -> AbstractEmitter:
    """Get C code emitter."""
    return CEmitter(self.preferences)

get_factory()

Get C code element factory.

Source code in src/multigen/backends/c/backend.py
def get_factory(self) -> AbstractFactory:
    """Get C code element factory."""
    return CFactory()

get_file_extension()

Return C source file extension.

Source code in src/multigen/backends/c/backend.py
def get_file_extension(self) -> str:
    """Return C source file extension."""
    return ".c"

get_name()

Return backend name.

Source code in src/multigen/backends/c/backend.py
def get_name(self) -> str:
    """Return backend name."""
    return "c"

get_optimizer()

Get C optimizer (delegates to compiler).

C optimization is handled by the compiler (gcc -O2, clang -O2, etc.) rather than at the code generation level.

Returns:

Type Description
AbstractOptimizer

NoOpOptimizer instance

Source code in src/multigen/backends/c/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get C optimizer (delegates to compiler).

    C optimization is handled by the compiler (gcc -O2, clang -O2, etc.)
    rather than at the code generation level.

    Returns:
        NoOpOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = NoOpOptimizer()
    return self._optimizer

C++ Backend

multigen.backends.cpp

C++ backend for MultiGen.

CppBackend

Bases: LanguageBackend

C++ language backend.

Source code in src/multigen/backends/cpp/backend.py
class CppBackend(LanguageBackend):
    """C++ language backend."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize C++ backend with preferences."""
        if preferences is None:
            preferences = CppPreferences()
        super().__init__(preferences)
        self._optimizer: Optional[NoOpOptimizer] = None

    def get_name(self) -> str:
        """Get the backend name."""
        return "cpp"

    def get_file_extension(self) -> str:
        """Get the file extension for C++ files."""
        return ".cpp"

    def get_factory(self) -> "AbstractFactory":
        """Get the C++ code element factory."""
        from .factory import CppFactory

        return CppFactory()

    def get_emitter(self) -> "AbstractEmitter":
        """Get the C++ code emitter."""
        from .emitter import CppEmitter

        return CppEmitter(self.preferences)

    def get_builder(self) -> "AbstractBuilder":
        """Get the C++ builder."""
        from .builder import CppBuilder

        return CppBuilder()

    def get_container_system(self) -> "AbstractContainerSystem":
        """Get the C++ container system."""
        from .containers import CppContainerSystem

        return CppContainerSystem()

    def get_optimizer(self) -> AbstractOptimizer:
        """Get C++ optimizer (delegates to compiler).

        C++ optimization is handled by the compiler (g++ -O2, clang++ -O2, etc.)
        rather than at the code generation level.

        Returns:
            NoOpOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = NoOpOptimizer()
        return self._optimizer

__init__(preferences=None)

Initialize C++ backend with preferences.

Source code in src/multigen/backends/cpp/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize C++ backend with preferences."""
    if preferences is None:
        preferences = CppPreferences()
    super().__init__(preferences)
    self._optimizer: Optional[NoOpOptimizer] = None

get_builder()

Get the C++ builder.

Source code in src/multigen/backends/cpp/backend.py
def get_builder(self) -> "AbstractBuilder":
    """Get the C++ builder."""
    from .builder import CppBuilder

    return CppBuilder()

get_container_system()

Get the C++ container system.

Source code in src/multigen/backends/cpp/backend.py
def get_container_system(self) -> "AbstractContainerSystem":
    """Get the C++ container system."""
    from .containers import CppContainerSystem

    return CppContainerSystem()

get_emitter()

Get the C++ code emitter.

Source code in src/multigen/backends/cpp/backend.py
def get_emitter(self) -> "AbstractEmitter":
    """Get the C++ code emitter."""
    from .emitter import CppEmitter

    return CppEmitter(self.preferences)

get_factory()

Get the C++ code element factory.

Source code in src/multigen/backends/cpp/backend.py
def get_factory(self) -> "AbstractFactory":
    """Get the C++ code element factory."""
    from .factory import CppFactory

    return CppFactory()

get_file_extension()

Get the file extension for C++ files.

Source code in src/multigen/backends/cpp/backend.py
def get_file_extension(self) -> str:
    """Get the file extension for C++ files."""
    return ".cpp"

get_name()

Get the backend name.

Source code in src/multigen/backends/cpp/backend.py
def get_name(self) -> str:
    """Get the backend name."""
    return "cpp"

get_optimizer()

Get C++ optimizer (delegates to compiler).

C++ optimization is handled by the compiler (g++ -O2, clang++ -O2, etc.) rather than at the code generation level.

Returns:

Type Description
AbstractOptimizer

NoOpOptimizer instance

Source code in src/multigen/backends/cpp/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get C++ optimizer (delegates to compiler).

    C++ optimization is handled by the compiler (g++ -O2, clang++ -O2, etc.)
    rather than at the code generation level.

    Returns:
        NoOpOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = NoOpOptimizer()
    return self._optimizer

Rust Backend

multigen.backends.rust

Rust backend for MultiGen - Clean Rust code generation.

RustBackend

Bases: LanguageBackend

Rust backend implementation for MultiGen.

Source code in src/multigen/backends/rust/backend.py
class RustBackend(LanguageBackend):
    """Rust backend implementation for MultiGen."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize Rust backend with preferences."""
        if preferences is None:
            preferences = RustPreferences()
        super().__init__(preferences)
        self._optimizer: Optional[NoOpOptimizer] = None

    def get_name(self) -> str:
        """Return backend name."""
        return "rust"

    def get_file_extension(self) -> str:
        """Return Rust source file extension."""
        return ".rs"

    def get_factory(self) -> AbstractFactory:
        """Get Rust code element factory."""
        return RustFactory()

    def get_emitter(self) -> AbstractEmitter:
        """Get Rust code emitter."""
        return RustEmitter(self.preferences)

    def get_builder(self) -> AbstractBuilder:
        """Get Rust build system."""
        return RustBuilder()

    def get_container_system(self) -> AbstractContainerSystem:
        """Get Rust container system."""
        return RustContainerSystem()

    def get_optimizer(self) -> AbstractOptimizer:
        """Get Rust optimizer (delegates to compiler).

        Rust optimization is handled by rustc/LLVM (rustc -O, --release, etc.)
        rather than at the code generation level.

        Returns:
            NoOpOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = NoOpOptimizer()
        return self._optimizer

__init__(preferences=None)

Initialize Rust backend with preferences.

Source code in src/multigen/backends/rust/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize Rust backend with preferences."""
    if preferences is None:
        preferences = RustPreferences()
    super().__init__(preferences)
    self._optimizer: Optional[NoOpOptimizer] = None

get_builder()

Get Rust build system.

Source code in src/multigen/backends/rust/backend.py
def get_builder(self) -> AbstractBuilder:
    """Get Rust build system."""
    return RustBuilder()

get_container_system()

Get Rust container system.

Source code in src/multigen/backends/rust/backend.py
def get_container_system(self) -> AbstractContainerSystem:
    """Get Rust container system."""
    return RustContainerSystem()

get_emitter()

Get Rust code emitter.

Source code in src/multigen/backends/rust/backend.py
def get_emitter(self) -> AbstractEmitter:
    """Get Rust code emitter."""
    return RustEmitter(self.preferences)

get_factory()

Get Rust code element factory.

Source code in src/multigen/backends/rust/backend.py
def get_factory(self) -> AbstractFactory:
    """Get Rust code element factory."""
    return RustFactory()

get_file_extension()

Return Rust source file extension.

Source code in src/multigen/backends/rust/backend.py
def get_file_extension(self) -> str:
    """Return Rust source file extension."""
    return ".rs"

get_name()

Return backend name.

Source code in src/multigen/backends/rust/backend.py
def get_name(self) -> str:
    """Return backend name."""
    return "rust"

get_optimizer()

Get Rust optimizer (delegates to compiler).

Rust optimization is handled by rustc/LLVM (rustc -O, --release, etc.) rather than at the code generation level.

Returns:

Type Description
AbstractOptimizer

NoOpOptimizer instance

Source code in src/multigen/backends/rust/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get Rust optimizer (delegates to compiler).

    Rust optimization is handled by rustc/LLVM (rustc -O, --release, etc.)
    rather than at the code generation level.

    Returns:
        NoOpOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = NoOpOptimizer()
    return self._optimizer

Go Backend

multigen.backends.go

Go backend for MultiGen - Clean Go code generation.

GoBackend

Bases: LanguageBackend

Go backend implementation for MultiGen.

Source code in src/multigen/backends/go/backend.py
class GoBackend(LanguageBackend):
    """Go backend implementation for MultiGen."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize Go backend with preferences."""
        if preferences is None:
            preferences = GoPreferences()
        super().__init__(preferences)
        self._optimizer: Optional[NoOpOptimizer] = None

    def get_name(self) -> str:
        """Return backend name."""
        return "go"

    def get_file_extension(self) -> str:
        """Return Go source file extension."""
        return ".go"

    def get_factory(self) -> AbstractFactory:
        """Get Go code element factory."""
        return GoFactory()

    def get_emitter(self) -> AbstractEmitter:
        """Get Go code emitter."""
        return GoEmitter(self.preferences)

    def get_builder(self) -> AbstractBuilder:
        """Get Go build system."""
        return GoBuilder()

    def get_container_system(self) -> AbstractContainerSystem:
        """Get Go container system."""
        return GoContainerSystem()

    def get_optimizer(self) -> AbstractOptimizer:
        """Get Go optimizer (delegates to compiler).

        Go optimization is handled by the Go compiler (go build)
        rather than at the code generation level.

        Returns:
            NoOpOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = NoOpOptimizer()
        return self._optimizer

__init__(preferences=None)

Initialize Go backend with preferences.

Source code in src/multigen/backends/go/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize Go backend with preferences."""
    if preferences is None:
        preferences = GoPreferences()
    super().__init__(preferences)
    self._optimizer: Optional[NoOpOptimizer] = None

get_builder()

Get Go build system.

Source code in src/multigen/backends/go/backend.py
def get_builder(self) -> AbstractBuilder:
    """Get Go build system."""
    return GoBuilder()

get_container_system()

Get Go container system.

Source code in src/multigen/backends/go/backend.py
def get_container_system(self) -> AbstractContainerSystem:
    """Get Go container system."""
    return GoContainerSystem()

get_emitter()

Get Go code emitter.

Source code in src/multigen/backends/go/backend.py
def get_emitter(self) -> AbstractEmitter:
    """Get Go code emitter."""
    return GoEmitter(self.preferences)

get_factory()

Get Go code element factory.

Source code in src/multigen/backends/go/backend.py
def get_factory(self) -> AbstractFactory:
    """Get Go code element factory."""
    return GoFactory()

get_file_extension()

Return Go source file extension.

Source code in src/multigen/backends/go/backend.py
def get_file_extension(self) -> str:
    """Return Go source file extension."""
    return ".go"

get_name()

Return backend name.

Source code in src/multigen/backends/go/backend.py
def get_name(self) -> str:
    """Return backend name."""
    return "go"

get_optimizer()

Get Go optimizer (delegates to compiler).

Go optimization is handled by the Go compiler (go build) rather than at the code generation level.

Returns:

Type Description
AbstractOptimizer

NoOpOptimizer instance

Source code in src/multigen/backends/go/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get Go optimizer (delegates to compiler).

    Go optimization is handled by the Go compiler (go build)
    rather than at the code generation level.

    Returns:
        NoOpOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = NoOpOptimizer()
    return self._optimizer

Haskell Backend

multigen.backends.haskell

Haskell backend for MultiGen code generation.

HaskellBackend

Bases: LanguageBackend

Haskell backend implementation for MultiGen.

Source code in src/multigen/backends/haskell/backend.py
class HaskellBackend(LanguageBackend):
    """Haskell backend implementation for MultiGen."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize Haskell backend with preferences."""
        if preferences is None:
            preferences = HaskellPreferences()
        super().__init__(preferences)
        self._optimizer: Optional[NoOpOptimizer] = None

    def get_name(self) -> str:
        """Return backend name."""
        return "haskell"

    def get_file_extension(self) -> str:
        """Return Haskell source file extension."""
        return ".hs"

    def get_factory(self) -> AbstractFactory:
        """Get Haskell code element factory."""
        return HaskellFactory()

    def get_emitter(self) -> AbstractEmitter:
        """Get Haskell code emitter."""
        return HaskellEmitter(self.preferences)

    def get_builder(self) -> AbstractBuilder:
        """Get Haskell build system."""
        return HaskellBuilder()

    def get_container_system(self) -> AbstractContainerSystem:
        """Get Haskell container system."""
        return HaskellContainerSystem()

    def get_optimizer(self) -> AbstractOptimizer:
        """Get Haskell optimizer (delegates to compiler).

        Haskell optimization is handled by GHC (ghc -O, -O2, etc.)
        rather than at the code generation level.

        Returns:
            NoOpOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = NoOpOptimizer()
        return self._optimizer

__init__(preferences=None)

Initialize Haskell backend with preferences.

Source code in src/multigen/backends/haskell/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize Haskell backend with preferences."""
    if preferences is None:
        preferences = HaskellPreferences()
    super().__init__(preferences)
    self._optimizer: Optional[NoOpOptimizer] = None

get_builder()

Get Haskell build system.

Source code in src/multigen/backends/haskell/backend.py
def get_builder(self) -> AbstractBuilder:
    """Get Haskell build system."""
    return HaskellBuilder()

get_container_system()

Get Haskell container system.

Source code in src/multigen/backends/haskell/backend.py
def get_container_system(self) -> AbstractContainerSystem:
    """Get Haskell container system."""
    return HaskellContainerSystem()

get_emitter()

Get Haskell code emitter.

Source code in src/multigen/backends/haskell/backend.py
def get_emitter(self) -> AbstractEmitter:
    """Get Haskell code emitter."""
    return HaskellEmitter(self.preferences)

get_factory()

Get Haskell code element factory.

Source code in src/multigen/backends/haskell/backend.py
def get_factory(self) -> AbstractFactory:
    """Get Haskell code element factory."""
    return HaskellFactory()

get_file_extension()

Return Haskell source file extension.

Source code in src/multigen/backends/haskell/backend.py
def get_file_extension(self) -> str:
    """Return Haskell source file extension."""
    return ".hs"

get_name()

Return backend name.

Source code in src/multigen/backends/haskell/backend.py
def get_name(self) -> str:
    """Return backend name."""
    return "haskell"

get_optimizer()

Get Haskell optimizer (delegates to compiler).

Haskell optimization is handled by GHC (ghc -O, -O2, etc.) rather than at the code generation level.

Returns:

Type Description
AbstractOptimizer

NoOpOptimizer instance

Source code in src/multigen/backends/haskell/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get Haskell optimizer (delegates to compiler).

    Haskell optimization is handled by GHC (ghc -O, -O2, etc.)
    rather than at the code generation level.

    Returns:
        NoOpOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = NoOpOptimizer()
    return self._optimizer

OCaml Backend

multigen.backends.ocaml

OCaml backend for MultiGen.

OCamlBackend

Bases: LanguageBackend

OCaml backend for MultiGen code generation.

Source code in src/multigen/backends/ocaml/backend.py
class OCamlBackend(LanguageBackend):
    """OCaml backend for MultiGen code generation."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize the OCaml backend with preferences."""
        if preferences is None:
            preferences = OCamlPreferences()
        super().__init__(preferences)
        # Ensure preferences is never None after initialization
        assert self.preferences is not None
        self._optimizer: Optional[NoOpOptimizer] = None

    def get_name(self) -> str:
        """Return backend name."""
        return "ocaml"

    def get_file_extension(self) -> str:
        """Get the file extension for OCaml files."""
        return ".ml"

    def get_factory(self) -> AbstractFactory:
        """Get OCaml code element factory."""
        return OCamlFactory()

    def get_emitter(self) -> AbstractEmitter:
        """Get OCaml code emitter."""
        return OCamlEmitter(self.preferences)

    def get_builder(self) -> AbstractBuilder:
        """Get OCaml build system."""
        return OCamlBuilder()

    def get_container_system(self) -> AbstractContainerSystem:
        """Get OCaml container system."""
        return OCamlContainerSystem()

    def get_optimizer(self) -> AbstractOptimizer:
        """Get OCaml optimizer (delegates to compiler).

        OCaml optimization is handled by ocamlopt (native compiler)
        rather than at the code generation level.

        Returns:
            NoOpOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = NoOpOptimizer()
        return self._optimizer

    def get_build_system(self) -> str:
        """Get the build system used by OCaml."""
        return "dune / ocamlc"

    def get_description(self) -> str:
        """Get a description of the OCaml backend."""
        return "Functional programming with OCaml standard library, pattern matching, and type safety"

    def is_available(self) -> bool:
        """Check if OCaml compiler is available."""
        import subprocess

        try:
            result = subprocess.run(["ocamlc", "-version"], capture_output=True, text=True, timeout=5)
            return result.returncode == 0
        except (subprocess.TimeoutExpired, FileNotFoundError):
            return False

    def get_version_info(self) -> str:
        """Get OCaml compiler version information."""
        import subprocess

        try:
            result = subprocess.run(["ocamlc", "-version"], capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return result.stdout.strip()
            else:
                return "Unknown version"
        except (subprocess.TimeoutExpired, FileNotFoundError):
            return "OCaml not available"

    def get_advanced_features(self) -> list[str]:
        """Get list of advanced features supported by the OCaml backend."""
        features = [
            "Object-oriented programming (classes, methods, constructors)",
            "Functional programming with immutable data structures",
            "Pattern matching over conditionals",
            "Tail recursion optimization",
            "String methods (upper, lower, strip, find, replace, split)",
            "List comprehensions with functional patterns",
            "Dictionary and set comprehensions",
            "Type annotations with OCaml type system",
            "Module system with nested organization",
            "OCaml standard library integration",
        ]

        # Add preference-specific features
        if self.preferences is not None and self.preferences.get("use_pattern_matching"):
            features.append("Idiomatic pattern matching syntax")

        if self.preferences is not None and self.preferences.get("curried_functions"):
            features.append("Curried function style")

        if self.preferences is not None and self.preferences.get("tail_recursion_opt"):
            features.append("Tail recursion optimization")

        if self.preferences is not None and self.preferences.get("polymorphic_variants"):
            features.append("Polymorphic variant support")

        return features

    def get_preference_summary(self) -> dict[str, Any]:
        """Get a summary of current preferences."""
        if self.preferences is None:
            return {}
        return {
            "OCaml Version": self.preferences.get("ocaml_version", "4.14"),
            "Modern Syntax": self.preferences.get("use_modern_syntax", True),
            "Pattern Matching": self.preferences.get("use_pattern_matching", True),
            "Immutable Data": self.preferences.get("prefer_immutable", True),
            "Curried Functions": self.preferences.get("curried_functions", True),
            "Type Annotations": self.preferences.get("type_annotations", True),
            "Module Structure": self.preferences.get("module_structure", "nested"),
            "List Operations": self.preferences.get("list_operations", "functional"),
            "String Handling": self.preferences.get("string_handling", "stdlib"),
            "Naming Convention": self.preferences.get("naming_convention", "snake_case"),
        }

__init__(preferences=None)

Initialize the OCaml backend with preferences.

Source code in src/multigen/backends/ocaml/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize the OCaml backend with preferences."""
    if preferences is None:
        preferences = OCamlPreferences()
    super().__init__(preferences)
    # Ensure preferences is never None after initialization
    assert self.preferences is not None
    self._optimizer: Optional[NoOpOptimizer] = None

get_advanced_features()

Get list of advanced features supported by the OCaml backend.

Source code in src/multigen/backends/ocaml/backend.py
def get_advanced_features(self) -> list[str]:
    """Get list of advanced features supported by the OCaml backend."""
    features = [
        "Object-oriented programming (classes, methods, constructors)",
        "Functional programming with immutable data structures",
        "Pattern matching over conditionals",
        "Tail recursion optimization",
        "String methods (upper, lower, strip, find, replace, split)",
        "List comprehensions with functional patterns",
        "Dictionary and set comprehensions",
        "Type annotations with OCaml type system",
        "Module system with nested organization",
        "OCaml standard library integration",
    ]

    # Add preference-specific features
    if self.preferences is not None and self.preferences.get("use_pattern_matching"):
        features.append("Idiomatic pattern matching syntax")

    if self.preferences is not None and self.preferences.get("curried_functions"):
        features.append("Curried function style")

    if self.preferences is not None and self.preferences.get("tail_recursion_opt"):
        features.append("Tail recursion optimization")

    if self.preferences is not None and self.preferences.get("polymorphic_variants"):
        features.append("Polymorphic variant support")

    return features

get_build_system()

Get the build system used by OCaml.

Source code in src/multigen/backends/ocaml/backend.py
def get_build_system(self) -> str:
    """Get the build system used by OCaml."""
    return "dune / ocamlc"

get_builder()

Get OCaml build system.

Source code in src/multigen/backends/ocaml/backend.py
def get_builder(self) -> AbstractBuilder:
    """Get OCaml build system."""
    return OCamlBuilder()

get_container_system()

Get OCaml container system.

Source code in src/multigen/backends/ocaml/backend.py
def get_container_system(self) -> AbstractContainerSystem:
    """Get OCaml container system."""
    return OCamlContainerSystem()

get_description()

Get a description of the OCaml backend.

Source code in src/multigen/backends/ocaml/backend.py
def get_description(self) -> str:
    """Get a description of the OCaml backend."""
    return "Functional programming with OCaml standard library, pattern matching, and type safety"

get_emitter()

Get OCaml code emitter.

Source code in src/multigen/backends/ocaml/backend.py
def get_emitter(self) -> AbstractEmitter:
    """Get OCaml code emitter."""
    return OCamlEmitter(self.preferences)

get_factory()

Get OCaml code element factory.

Source code in src/multigen/backends/ocaml/backend.py
def get_factory(self) -> AbstractFactory:
    """Get OCaml code element factory."""
    return OCamlFactory()

get_file_extension()

Get the file extension for OCaml files.

Source code in src/multigen/backends/ocaml/backend.py
def get_file_extension(self) -> str:
    """Get the file extension for OCaml files."""
    return ".ml"

get_name()

Return backend name.

Source code in src/multigen/backends/ocaml/backend.py
def get_name(self) -> str:
    """Return backend name."""
    return "ocaml"

get_optimizer()

Get OCaml optimizer (delegates to compiler).

OCaml optimization is handled by ocamlopt (native compiler) rather than at the code generation level.

Returns:

Type Description
AbstractOptimizer

NoOpOptimizer instance

Source code in src/multigen/backends/ocaml/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get OCaml optimizer (delegates to compiler).

    OCaml optimization is handled by ocamlopt (native compiler)
    rather than at the code generation level.

    Returns:
        NoOpOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = NoOpOptimizer()
    return self._optimizer

get_preference_summary()

Get a summary of current preferences.

Source code in src/multigen/backends/ocaml/backend.py
def get_preference_summary(self) -> dict[str, Any]:
    """Get a summary of current preferences."""
    if self.preferences is None:
        return {}
    return {
        "OCaml Version": self.preferences.get("ocaml_version", "4.14"),
        "Modern Syntax": self.preferences.get("use_modern_syntax", True),
        "Pattern Matching": self.preferences.get("use_pattern_matching", True),
        "Immutable Data": self.preferences.get("prefer_immutable", True),
        "Curried Functions": self.preferences.get("curried_functions", True),
        "Type Annotations": self.preferences.get("type_annotations", True),
        "Module Structure": self.preferences.get("module_structure", "nested"),
        "List Operations": self.preferences.get("list_operations", "functional"),
        "String Handling": self.preferences.get("string_handling", "stdlib"),
        "Naming Convention": self.preferences.get("naming_convention", "snake_case"),
    }

get_version_info()

Get OCaml compiler version information.

Source code in src/multigen/backends/ocaml/backend.py
def get_version_info(self) -> str:
    """Get OCaml compiler version information."""
    import subprocess

    try:
        result = subprocess.run(["ocamlc", "-version"], capture_output=True, text=True, timeout=5)
        if result.returncode == 0:
            return result.stdout.strip()
        else:
            return "Unknown version"
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return "OCaml not available"

is_available()

Check if OCaml compiler is available.

Source code in src/multigen/backends/ocaml/backend.py
def is_available(self) -> bool:
    """Check if OCaml compiler is available."""
    import subprocess

    try:
        result = subprocess.run(["ocamlc", "-version"], capture_output=True, text=True, timeout=5)
        return result.returncode == 0
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return False

OCamlBuilder

Bases: AbstractBuilder

Builder for OCaml code compilation and execution.

Source code in src/multigen/backends/ocaml/builder.py
class OCamlBuilder(AbstractBuilder):
    """Builder for OCaml code compilation and execution."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize the OCaml builder with preferences."""
        self.preferences = preferences or OCamlPreferences()

    def build(self, output_file: str, makefile: bool = False) -> bool:
        """Build the OCaml code.

        Args:
            output_file: The OCaml source file to compile
            makefile: Whether to generate a dune-project file instead of direct compilation

        Returns:
            True if build succeeded, False otherwise
        """
        if makefile:
            return self._generate_dune_project(output_file)
        else:
            return self._compile_direct_internal(output_file)

    def _compile_direct_internal(self, output_file: str) -> bool:
        """Compile OCaml code directly using ocamlc (internal method)."""
        base_path = Path(output_file).parent
        runtime_path = base_path / "multigen_runtime.ml"

        # Copy runtime file if it doesn't exist
        if not runtime_path.exists():
            self._copy_runtime_files(base_path)

        # Compile with OCaml compiler
        executable = output_file.replace(".ml", "")
        if _has_opam_initialized():
            cmd = ["opam", "exec", "--", "ocamlc", "-o", executable, str(runtime_path), output_file]
        else:
            cmd = ["ocamlc", "-o", executable, str(runtime_path), output_file]

        result = self._run_command(cmd)
        return result.success

    def _generate_dune_project(self, output_file: str) -> bool:
        """Generate a dune-project file for the OCaml project."""
        base_path = Path(output_file).parent
        project_name = Path(output_file).stem

        # Copy runtime files
        self._copy_runtime_files(base_path)

        # Generate dune-project
        dune_project_content = f"""(lang dune 3.0)

(package
 (name {project_name})
 (depends ocaml dune))
"""

        dune_project_path = base_path / "dune-project"
        with open(dune_project_path, "w") as f:
            f.write(dune_project_content)

        # Generate dune file
        dune_content = f"""(executable
 (public_name {project_name})
 (name {project_name})
 (modules multigen_runtime {project_name}))
"""

        dune_file_path = base_path / "dune"
        with open(dune_file_path, "w") as f:
            f.write(dune_content)

        return True

    def _copy_runtime_files(self, target_dir: Path) -> None:
        """Copy OCaml runtime files to the target directory."""
        # Copy all .ml files from runtime directory using base class helper
        for runtime_file in self._get_runtime_files("*.ml"):
            target_file = target_dir / runtime_file.name
            if not target_file.exists():
                shutil.copy2(runtime_file, target_file)

    def get_build_command(self, output_file: str) -> list[str]:
        """Get the command to build the OCaml file."""
        base_name = Path(output_file).stem
        return ["opam", "exec", "--", "ocamlc", "-o", base_name, "multigen_runtime.ml", output_file]

    def get_run_command(self, output_file: str) -> list[str]:
        """Get the command to run the compiled OCaml executable."""
        executable = Path(output_file).stem
        return [f"./{executable}"]

    def clean(self, output_file: str) -> bool:
        """Clean build artifacts."""
        base_path = Path(output_file).parent
        base_name = Path(output_file).stem

        # Remove common OCaml build artifacts
        artifacts = [
            base_name,  # executable
            f"{base_name}.cmi",  # compiled interface
            f"{base_name}.cmo",  # compiled object
            "multigen_runtime.cmi",
            "multigen_runtime.cmo",
            "_build",  # dune build directory
            "dune-project",
            "dune",
        ]

        removed_count = 0
        for artifact in artifacts:
            artifact_path = base_path / artifact
            try:
                if artifact_path.is_file():
                    artifact_path.unlink()
                    removed_count += 1
                elif artifact_path.is_dir():
                    import shutil

                    shutil.rmtree(artifact_path)
                    removed_count += 1
            except Exception:
                continue

        return True

    def generate_build_file(self, source_files: list[str], target_name: str) -> str:
        """Generate dune-project build configuration."""
        dune_project_content = f"""(lang dune 3.0)

(package
 (name {target_name})
 (depends ocaml dune))
"""

        dune_content = f"""(executable
 (public_name {target_name})
 (name {target_name})
 (modules multigen_runtime {" ".join(Path(f).stem for f in source_files)}))
"""

        return dune_project_content + "\n" + dune_content

    def get_build_filename(self) -> str:
        """Get build file name for OCaml."""
        return "dune-project"

    def compile_direct(self, source_file: str, output_dir: str, **kwargs: Any) -> bool:
        """Compile OCaml source directly using ocamlc."""
        # Resolve paths using base class helper
        paths = self._resolve_paths(source_file, output_dir)
        source_dir = paths.source_path.parent

        # Copy runtime file to source directory (OCaml looks for modules there)
        runtime_path = source_dir / "multigen_runtime.ml"
        if not runtime_path.exists():
            self._copy_runtime_files(source_dir)

        # Build command (use opam if initialized, otherwise direct ocamlc)
        if _has_opam_initialized():
            cmd = [
                "opam",
                "exec",
                "--",
                "ocamlc",
                "-I",
                str(source_dir),
                "-o",
                str(paths.executable_path),
                str(runtime_path),
                str(paths.source_path),
            ]
        else:
            cmd = [
                "ocamlc",
                "-I",
                str(source_dir),
                "-o",
                str(paths.executable_path),
                str(runtime_path),
                str(paths.source_path),
            ]

        # Run compilation using base class helper
        result = self._run_command(cmd)
        return result.success

    def get_compile_flags(self) -> list[str]:
        """Get compilation flags for OCaml."""
        return ["-o"]

__init__(preferences=None)

Initialize the OCaml builder with preferences.

Source code in src/multigen/backends/ocaml/builder.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize the OCaml builder with preferences."""
    self.preferences = preferences or OCamlPreferences()

build(output_file, makefile=False)

Build the OCaml code.

Parameters:

Name Type Description Default
output_file str

The OCaml source file to compile

required
makefile bool

Whether to generate a dune-project file instead of direct compilation

False

Returns:

Type Description
bool

True if build succeeded, False otherwise

Source code in src/multigen/backends/ocaml/builder.py
def build(self, output_file: str, makefile: bool = False) -> bool:
    """Build the OCaml code.

    Args:
        output_file: The OCaml source file to compile
        makefile: Whether to generate a dune-project file instead of direct compilation

    Returns:
        True if build succeeded, False otherwise
    """
    if makefile:
        return self._generate_dune_project(output_file)
    else:
        return self._compile_direct_internal(output_file)

clean(output_file)

Clean build artifacts.

Source code in src/multigen/backends/ocaml/builder.py
def clean(self, output_file: str) -> bool:
    """Clean build artifacts."""
    base_path = Path(output_file).parent
    base_name = Path(output_file).stem

    # Remove common OCaml build artifacts
    artifacts = [
        base_name,  # executable
        f"{base_name}.cmi",  # compiled interface
        f"{base_name}.cmo",  # compiled object
        "multigen_runtime.cmi",
        "multigen_runtime.cmo",
        "_build",  # dune build directory
        "dune-project",
        "dune",
    ]

    removed_count = 0
    for artifact in artifacts:
        artifact_path = base_path / artifact
        try:
            if artifact_path.is_file():
                artifact_path.unlink()
                removed_count += 1
            elif artifact_path.is_dir():
                import shutil

                shutil.rmtree(artifact_path)
                removed_count += 1
        except Exception:
            continue

    return True

compile_direct(source_file, output_dir, **kwargs)

Compile OCaml source directly using ocamlc.

Source code in src/multigen/backends/ocaml/builder.py
def compile_direct(self, source_file: str, output_dir: str, **kwargs: Any) -> bool:
    """Compile OCaml source directly using ocamlc."""
    # Resolve paths using base class helper
    paths = self._resolve_paths(source_file, output_dir)
    source_dir = paths.source_path.parent

    # Copy runtime file to source directory (OCaml looks for modules there)
    runtime_path = source_dir / "multigen_runtime.ml"
    if not runtime_path.exists():
        self._copy_runtime_files(source_dir)

    # Build command (use opam if initialized, otherwise direct ocamlc)
    if _has_opam_initialized():
        cmd = [
            "opam",
            "exec",
            "--",
            "ocamlc",
            "-I",
            str(source_dir),
            "-o",
            str(paths.executable_path),
            str(runtime_path),
            str(paths.source_path),
        ]
    else:
        cmd = [
            "ocamlc",
            "-I",
            str(source_dir),
            "-o",
            str(paths.executable_path),
            str(runtime_path),
            str(paths.source_path),
        ]

    # Run compilation using base class helper
    result = self._run_command(cmd)
    return result.success

generate_build_file(source_files, target_name)

Generate dune-project build configuration.

Source code in src/multigen/backends/ocaml/builder.py
    def generate_build_file(self, source_files: list[str], target_name: str) -> str:
        """Generate dune-project build configuration."""
        dune_project_content = f"""(lang dune 3.0)

(package
 (name {target_name})
 (depends ocaml dune))
"""

        dune_content = f"""(executable
 (public_name {target_name})
 (name {target_name})
 (modules multigen_runtime {" ".join(Path(f).stem for f in source_files)}))
"""

        return dune_project_content + "\n" + dune_content

get_build_command(output_file)

Get the command to build the OCaml file.

Source code in src/multigen/backends/ocaml/builder.py
def get_build_command(self, output_file: str) -> list[str]:
    """Get the command to build the OCaml file."""
    base_name = Path(output_file).stem
    return ["opam", "exec", "--", "ocamlc", "-o", base_name, "multigen_runtime.ml", output_file]

get_build_filename()

Get build file name for OCaml.

Source code in src/multigen/backends/ocaml/builder.py
def get_build_filename(self) -> str:
    """Get build file name for OCaml."""
    return "dune-project"

get_compile_flags()

Get compilation flags for OCaml.

Source code in src/multigen/backends/ocaml/builder.py
def get_compile_flags(self) -> list[str]:
    """Get compilation flags for OCaml."""
    return ["-o"]

get_run_command(output_file)

Get the command to run the compiled OCaml executable.

Source code in src/multigen/backends/ocaml/builder.py
def get_run_command(self, output_file: str) -> list[str]:
    """Get the command to run the compiled OCaml executable."""
    executable = Path(output_file).stem
    return [f"./{executable}"]

OCamlContainerSystem

Bases: AbstractContainerSystem

Container system for OCaml using standard library collections.

Source code in src/multigen/backends/ocaml/containers.py
class OCamlContainerSystem(AbstractContainerSystem):
    """Container system for OCaml using standard library collections."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize the OCaml container system with preferences."""
        self.preferences = preferences or OCamlPreferences()

    def get_list_type(self, element_type: str) -> str:
        """Get OCaml list type declaration."""
        ocaml_type = self._map_python_type_to_ocaml(element_type)
        return f"{ocaml_type} list"

    def get_dict_type(self, key_type: str, value_type: str) -> str:
        """Get OCaml map type declaration."""
        ocaml_key_type = self._map_python_type_to_ocaml(key_type)
        ocaml_value_type = self._map_python_type_to_ocaml(value_type)

        # Use Map module for dictionaries
        if self.preferences.get("hashtables") == "stdlib":
            return f"({ocaml_key_type}, {ocaml_value_type}) Hashtbl.t"
        else:
            # Default to Map for functional approach
            return f"{ocaml_value_type} Map.Make(struct type t = {ocaml_key_type} let compare = compare end).t"

    def get_set_type(self, element_type: str) -> str:
        """Get OCaml set type declaration."""
        ocaml_type = self._map_python_type_to_ocaml(element_type)
        return f"{ocaml_type} Set.Make(struct type t = {ocaml_type} let compare = compare end).t"

    def get_list_operations(self) -> dict[str, str]:
        """Get OCaml list operation mappings."""
        if self.preferences.get("list_operations") == "functional":
            return {
                "append": "List.append",
                "length": "List.length",
                "map": "List.map",
                "filter": "List.filter",
                "fold": "List.fold_left",
                "reverse": "List.rev",
                "concat": "List.concat",
                "head": "List.hd",
                "tail": "List.tl",
            }
        else:
            # Use runtime functions for consistency
            return {
                "append": "@",  # List concatenation operator
                "length": "len_list",
                "map": "list_comprehension",
                "filter": "list_comprehension_with_filter",
                "fold": "List.fold_left",
                "reverse": "List.rev",
                "concat": "List.concat",
                "head": "List.hd",
                "tail": "List.tl",
            }

    def get_dict_operations(self) -> dict[str, str]:
        """Get OCaml dictionary operation mappings."""
        if self.preferences.get("hashtables") == "stdlib":
            return {
                "create": "Hashtbl.create",
                "add": "Hashtbl.add",
                "find": "Hashtbl.find",
                "remove": "Hashtbl.remove",
                "mem": "Hashtbl.mem",
                "length": "Hashtbl.length",
                "iter": "Hashtbl.iter",
                "fold": "Hashtbl.fold",
            }
        else:
            # Map module operations
            return {
                "create": "Map.empty",
                "add": "Map.add",
                "find": "Map.find",
                "remove": "Map.remove",
                "mem": "Map.mem",
                "length": "Map.cardinal",
                "iter": "Map.iter",
                "fold": "Map.fold",
            }

    def get_set_operations(self) -> dict[str, str]:
        """Get OCaml set operation mappings."""
        return {
            "create": "Set.empty",
            "add": "Set.add",
            "remove": "Set.remove",
            "mem": "Set.mem",
            "union": "Set.union",
            "inter": "Set.inter",
            "diff": "Set.diff",
            "subset": "Set.subset",
            "length": "Set.cardinal",
            "iter": "Set.iter",
            "fold": "Set.fold",
        }

    def generate_list_literal(self, elements: list[Any], element_type: str) -> str:
        """Generate OCaml list literal."""
        if not elements:
            return "[]"

        # Convert elements to OCaml syntax
        ocaml_elements = []
        for element in elements:
            if isinstance(element, str) and element_type in ["str", "string"]:
                ocaml_elements.append(f'"{element}"')
            else:
                ocaml_elements.append(str(element))

        return f"[{'; '.join(ocaml_elements)}]"

    def generate_dict_literal(self, items: dict[Any, Any], key_type: str, value_type: str) -> str:
        """Generate OCaml dictionary literal."""
        if not items:
            if self.preferences.get("hashtables") == "stdlib":
                return "Hashtbl.create 16"
            else:
                return "Map.empty"

        # For non-empty dictionaries, we need to build them step by step
        if self.preferences.get("hashtables") == "stdlib":
            # Hashtbl approach
            pairs = []
            for key, value in items.items():
                key_str = f'"{key}"' if key_type in ["str", "string"] else str(key)
                value_str = f'"{value}"' if value_type in ["str", "string"] else str(value)
                pairs.append(f"({key_str}, {value_str})")
            return f"let dict = Hashtbl.create {len(items)} in List.iter (fun (k, v) -> Hashtbl.add dict k v) [{'; '.join(pairs)}]; dict"
        else:
            # Map approach
            pairs = []
            for key, value in items.items():
                key_str = f'"{key}"' if key_type in ["str", "string"] else str(key)
                value_str = f'"{value}"' if value_type in ["str", "string"] else str(value)
                pairs.append(f"Map.add {key_str} {value_str}")
            return f"({' ('.join(pairs)} Map.empty{')' * len(pairs)})"

    def generate_set_literal(self, elements: list[Any], element_type: str) -> str:
        """Generate OCaml set literal."""
        if not elements:
            return "Set.empty"

        # Convert elements and build set
        ocaml_elements = []
        for element in elements:
            if isinstance(element, str) and element_type in ["str", "string"]:
                ocaml_elements.append(f'"{element}"')
            else:
                ocaml_elements.append(str(element))

        adds = " ".join(f"Set.add {elem}" for elem in ocaml_elements)
        return f"({adds} Set.empty{')' * len(ocaml_elements)})"

    def _map_python_type_to_ocaml(self, python_type: str) -> str:
        """Map Python type to OCaml type."""
        type_mapping = {
            "int": "int",
            "float": "float",
            "str": "string",
            "string": "string",
            "bool": "bool",
            "list": "list",
            "dict": "map",
            "set": "set",
            "tuple": "tuple",
            "None": "unit",
            "Any": "any",
        }
        return type_mapping.get(python_type, python_type)

    def get_include_headers(self) -> list[str]:
        """Get required include headers for OCaml."""
        # OCaml doesn't use include headers like C/C++
        # Modules are opened directly in the source
        return []

    def get_container_includes(self, container_types: set[str]) -> list[str]:
        """Get specific container module opens for OCaml."""
        includes = []

        if any("list" in t for t in container_types):
            includes.append("open List")

        if any("map" in t.lower() or "dict" in t.lower() for t in container_types):
            if self.preferences.get("hashtables") == "stdlib":
                includes.append("open Hashtbl")
            else:
                includes.append("module StringMap = Map.Make(String)")
                includes.append("module IntMap = Map.Make(struct type t = int let compare = compare end)")

        if any("set" in t.lower() for t in container_types):
            includes.append("module StringSet = Set.Make(String)")
            includes.append("module IntSet = Set.Make(struct type t = int let compare = compare end)")

        return includes

    def generate_container_operations(self, container_type: str, operations: list[str]) -> str:
        """Generate container-specific operations code."""
        if "list" in container_type.lower():
            ops = self.get_list_operations()
        elif "dict" in container_type.lower() or "map" in container_type.lower():
            ops = self.get_dict_operations()
        elif "set" in container_type.lower():
            ops = self.get_set_operations()
        else:
            return ""

        code_lines = []
        for operation in operations:
            if operation in ops:
                code_lines.append(f"(* {operation}: {ops[operation]} *)")

        return "\n".join(code_lines)

    def get_required_imports(self) -> list[str]:
        """Get imports required for container operations."""
        imports = []

        if self.preferences.get("hashtables") == "stdlib":
            imports.append("open Hashtbl")
        else:
            imports.append("module StringMap = Map.Make(String)")

        imports.extend(["open List", "module StringSet = Set.Make(String)"])

        return imports

__init__(preferences=None)

Initialize the OCaml container system with preferences.

Source code in src/multigen/backends/ocaml/containers.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize the OCaml container system with preferences."""
    self.preferences = preferences or OCamlPreferences()

generate_container_operations(container_type, operations)

Generate container-specific operations code.

Source code in src/multigen/backends/ocaml/containers.py
def generate_container_operations(self, container_type: str, operations: list[str]) -> str:
    """Generate container-specific operations code."""
    if "list" in container_type.lower():
        ops = self.get_list_operations()
    elif "dict" in container_type.lower() or "map" in container_type.lower():
        ops = self.get_dict_operations()
    elif "set" in container_type.lower():
        ops = self.get_set_operations()
    else:
        return ""

    code_lines = []
    for operation in operations:
        if operation in ops:
            code_lines.append(f"(* {operation}: {ops[operation]} *)")

    return "\n".join(code_lines)

generate_dict_literal(items, key_type, value_type)

Generate OCaml dictionary literal.

Source code in src/multigen/backends/ocaml/containers.py
def generate_dict_literal(self, items: dict[Any, Any], key_type: str, value_type: str) -> str:
    """Generate OCaml dictionary literal."""
    if not items:
        if self.preferences.get("hashtables") == "stdlib":
            return "Hashtbl.create 16"
        else:
            return "Map.empty"

    # For non-empty dictionaries, we need to build them step by step
    if self.preferences.get("hashtables") == "stdlib":
        # Hashtbl approach
        pairs = []
        for key, value in items.items():
            key_str = f'"{key}"' if key_type in ["str", "string"] else str(key)
            value_str = f'"{value}"' if value_type in ["str", "string"] else str(value)
            pairs.append(f"({key_str}, {value_str})")
        return f"let dict = Hashtbl.create {len(items)} in List.iter (fun (k, v) -> Hashtbl.add dict k v) [{'; '.join(pairs)}]; dict"
    else:
        # Map approach
        pairs = []
        for key, value in items.items():
            key_str = f'"{key}"' if key_type in ["str", "string"] else str(key)
            value_str = f'"{value}"' if value_type in ["str", "string"] else str(value)
            pairs.append(f"Map.add {key_str} {value_str}")
        return f"({' ('.join(pairs)} Map.empty{')' * len(pairs)})"

generate_list_literal(elements, element_type)

Generate OCaml list literal.

Source code in src/multigen/backends/ocaml/containers.py
def generate_list_literal(self, elements: list[Any], element_type: str) -> str:
    """Generate OCaml list literal."""
    if not elements:
        return "[]"

    # Convert elements to OCaml syntax
    ocaml_elements = []
    for element in elements:
        if isinstance(element, str) and element_type in ["str", "string"]:
            ocaml_elements.append(f'"{element}"')
        else:
            ocaml_elements.append(str(element))

    return f"[{'; '.join(ocaml_elements)}]"

generate_set_literal(elements, element_type)

Generate OCaml set literal.

Source code in src/multigen/backends/ocaml/containers.py
def generate_set_literal(self, elements: list[Any], element_type: str) -> str:
    """Generate OCaml set literal."""
    if not elements:
        return "Set.empty"

    # Convert elements and build set
    ocaml_elements = []
    for element in elements:
        if isinstance(element, str) and element_type in ["str", "string"]:
            ocaml_elements.append(f'"{element}"')
        else:
            ocaml_elements.append(str(element))

    adds = " ".join(f"Set.add {elem}" for elem in ocaml_elements)
    return f"({adds} Set.empty{')' * len(ocaml_elements)})"

get_container_includes(container_types)

Get specific container module opens for OCaml.

Source code in src/multigen/backends/ocaml/containers.py
def get_container_includes(self, container_types: set[str]) -> list[str]:
    """Get specific container module opens for OCaml."""
    includes = []

    if any("list" in t for t in container_types):
        includes.append("open List")

    if any("map" in t.lower() or "dict" in t.lower() for t in container_types):
        if self.preferences.get("hashtables") == "stdlib":
            includes.append("open Hashtbl")
        else:
            includes.append("module StringMap = Map.Make(String)")
            includes.append("module IntMap = Map.Make(struct type t = int let compare = compare end)")

    if any("set" in t.lower() for t in container_types):
        includes.append("module StringSet = Set.Make(String)")
        includes.append("module IntSet = Set.Make(struct type t = int let compare = compare end)")

    return includes

get_dict_operations()

Get OCaml dictionary operation mappings.

Source code in src/multigen/backends/ocaml/containers.py
def get_dict_operations(self) -> dict[str, str]:
    """Get OCaml dictionary operation mappings."""
    if self.preferences.get("hashtables") == "stdlib":
        return {
            "create": "Hashtbl.create",
            "add": "Hashtbl.add",
            "find": "Hashtbl.find",
            "remove": "Hashtbl.remove",
            "mem": "Hashtbl.mem",
            "length": "Hashtbl.length",
            "iter": "Hashtbl.iter",
            "fold": "Hashtbl.fold",
        }
    else:
        # Map module operations
        return {
            "create": "Map.empty",
            "add": "Map.add",
            "find": "Map.find",
            "remove": "Map.remove",
            "mem": "Map.mem",
            "length": "Map.cardinal",
            "iter": "Map.iter",
            "fold": "Map.fold",
        }

get_dict_type(key_type, value_type)

Get OCaml map type declaration.

Source code in src/multigen/backends/ocaml/containers.py
def get_dict_type(self, key_type: str, value_type: str) -> str:
    """Get OCaml map type declaration."""
    ocaml_key_type = self._map_python_type_to_ocaml(key_type)
    ocaml_value_type = self._map_python_type_to_ocaml(value_type)

    # Use Map module for dictionaries
    if self.preferences.get("hashtables") == "stdlib":
        return f"({ocaml_key_type}, {ocaml_value_type}) Hashtbl.t"
    else:
        # Default to Map for functional approach
        return f"{ocaml_value_type} Map.Make(struct type t = {ocaml_key_type} let compare = compare end).t"

get_include_headers()

Get required include headers for OCaml.

Source code in src/multigen/backends/ocaml/containers.py
def get_include_headers(self) -> list[str]:
    """Get required include headers for OCaml."""
    # OCaml doesn't use include headers like C/C++
    # Modules are opened directly in the source
    return []

get_list_operations()

Get OCaml list operation mappings.

Source code in src/multigen/backends/ocaml/containers.py
def get_list_operations(self) -> dict[str, str]:
    """Get OCaml list operation mappings."""
    if self.preferences.get("list_operations") == "functional":
        return {
            "append": "List.append",
            "length": "List.length",
            "map": "List.map",
            "filter": "List.filter",
            "fold": "List.fold_left",
            "reverse": "List.rev",
            "concat": "List.concat",
            "head": "List.hd",
            "tail": "List.tl",
        }
    else:
        # Use runtime functions for consistency
        return {
            "append": "@",  # List concatenation operator
            "length": "len_list",
            "map": "list_comprehension",
            "filter": "list_comprehension_with_filter",
            "fold": "List.fold_left",
            "reverse": "List.rev",
            "concat": "List.concat",
            "head": "List.hd",
            "tail": "List.tl",
        }

get_list_type(element_type)

Get OCaml list type declaration.

Source code in src/multigen/backends/ocaml/containers.py
def get_list_type(self, element_type: str) -> str:
    """Get OCaml list type declaration."""
    ocaml_type = self._map_python_type_to_ocaml(element_type)
    return f"{ocaml_type} list"

get_required_imports()

Get imports required for container operations.

Source code in src/multigen/backends/ocaml/containers.py
def get_required_imports(self) -> list[str]:
    """Get imports required for container operations."""
    imports = []

    if self.preferences.get("hashtables") == "stdlib":
        imports.append("open Hashtbl")
    else:
        imports.append("module StringMap = Map.Make(String)")

    imports.extend(["open List", "module StringSet = Set.Make(String)"])

    return imports

get_set_operations()

Get OCaml set operation mappings.

Source code in src/multigen/backends/ocaml/containers.py
def get_set_operations(self) -> dict[str, str]:
    """Get OCaml set operation mappings."""
    return {
        "create": "Set.empty",
        "add": "Set.add",
        "remove": "Set.remove",
        "mem": "Set.mem",
        "union": "Set.union",
        "inter": "Set.inter",
        "diff": "Set.diff",
        "subset": "Set.subset",
        "length": "Set.cardinal",
        "iter": "Set.iter",
        "fold": "Set.fold",
    }

get_set_type(element_type)

Get OCaml set type declaration.

Source code in src/multigen/backends/ocaml/containers.py
def get_set_type(self, element_type: str) -> str:
    """Get OCaml set type declaration."""
    ocaml_type = self._map_python_type_to_ocaml(element_type)
    return f"{ocaml_type} Set.Make(struct type t = {ocaml_type} let compare = compare end).t"

OCamlEmitter

Bases: AbstractEmitter

OCaml code emitter using the Python-to-OCaml converter.

Source code in src/multigen/backends/ocaml/emitter.py
class OCamlEmitter(AbstractEmitter):
    """OCaml code emitter using the Python-to-OCaml converter."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize OCaml emitter with optional preferences."""
        super().__init__(preferences)
        self.converter = MultiGenPythonToOCamlConverter(preferences)

    def emit_code(self, ast_node: ast.AST) -> str:
        """Generate OCaml code from Python AST."""
        if isinstance(ast_node, ast.Module):
            return self.converter._convert_module(ast_node)
        else:
            raise ValueError("Expected ast.Module node")

    def emit_from_source(self, source_code: str) -> str:
        """Generate OCaml code from Python source."""
        return self.converter.convert_code(source_code)

    def emit_function(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> str:
        """Generate complete function in OCaml."""
        return "\n".join(self.converter._convert_function_def(func_node))

    def emit_module(self, source_code: str, analysis_result: Optional[Any] = None, semantic_mapping: Any = None) -> str:
        """Generate complete module/file in OCaml."""
        # Note: semantic_mapping from Phase 4 available for future optimization
        return self.converter.convert_code(source_code)

    def map_python_type(self, python_type: str) -> str:
        """Map Python type to OCaml type."""
        return self.converter.type_map.get(python_type, "'a")

    def can_use_simple_emission(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> bool:
        """Determine if function can use simple emission strategy."""
        # For now, always use the full converter
        return False

__init__(preferences=None)

Initialize OCaml emitter with optional preferences.

Source code in src/multigen/backends/ocaml/emitter.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize OCaml emitter with optional preferences."""
    super().__init__(preferences)
    self.converter = MultiGenPythonToOCamlConverter(preferences)

can_use_simple_emission(func_node, type_context)

Determine if function can use simple emission strategy.

Source code in src/multigen/backends/ocaml/emitter.py
def can_use_simple_emission(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> bool:
    """Determine if function can use simple emission strategy."""
    # For now, always use the full converter
    return False

emit_code(ast_node)

Generate OCaml code from Python AST.

Source code in src/multigen/backends/ocaml/emitter.py
def emit_code(self, ast_node: ast.AST) -> str:
    """Generate OCaml code from Python AST."""
    if isinstance(ast_node, ast.Module):
        return self.converter._convert_module(ast_node)
    else:
        raise ValueError("Expected ast.Module node")

emit_from_source(source_code)

Generate OCaml code from Python source.

Source code in src/multigen/backends/ocaml/emitter.py
def emit_from_source(self, source_code: str) -> str:
    """Generate OCaml code from Python source."""
    return self.converter.convert_code(source_code)

emit_function(func_node, type_context)

Generate complete function in OCaml.

Source code in src/multigen/backends/ocaml/emitter.py
def emit_function(self, func_node: ast.FunctionDef, type_context: dict[str, str]) -> str:
    """Generate complete function in OCaml."""
    return "\n".join(self.converter._convert_function_def(func_node))

emit_module(source_code, analysis_result=None, semantic_mapping=None)

Generate complete module/file in OCaml.

Source code in src/multigen/backends/ocaml/emitter.py
def emit_module(self, source_code: str, analysis_result: Optional[Any] = None, semantic_mapping: Any = None) -> str:
    """Generate complete module/file in OCaml."""
    # Note: semantic_mapping from Phase 4 available for future optimization
    return self.converter.convert_code(source_code)

map_python_type(python_type)

Map Python type to OCaml type.

Source code in src/multigen/backends/ocaml/emitter.py
def map_python_type(self, python_type: str) -> str:
    """Map Python type to OCaml type."""
    return self.converter.type_map.get(python_type, "'a")

OCamlFactory

Bases: AbstractFactory

Factory for creating OCaml backend components.

Source code in src/multigen/backends/ocaml/factory.py
class OCamlFactory(AbstractFactory):
    """Factory for creating OCaml backend components."""

    def __init__(self, preferences: Optional[BackendPreferences] = None):
        """Initialize the OCaml factory with preferences."""
        self.preferences = preferences or OCamlPreferences()

    def create_variable(self, name: str, type_name: str, value: Optional[str] = None) -> str:
        """Create OCaml variable declaration."""
        ocaml_type = self._map_python_type_to_ocaml(type_name)
        if value:
            return f"let {name} : {ocaml_type} = {value}"
        else:
            return f"let {name} : {ocaml_type}"

    def create_function_signature(self, name: str, params: list[tuple[str, str]], return_type: str) -> str:
        """Create OCaml function signature."""
        ocaml_return_type = self._map_python_type_to_ocaml(return_type)

        if not params:
            return f"let {name} () : {ocaml_return_type}"

        param_types = []
        for _param_name, param_type in params:
            ocaml_param_type = self._map_python_type_to_ocaml(param_type)
            param_types.append(ocaml_param_type)

        type_signature = " -> ".join(param_types + [ocaml_return_type])
        param_names = " ".join(param_name for param_name, _ in params)

        return f"let {name} {param_names} : {type_signature}"

    def create_comment(self, text: str) -> str:
        """Create OCaml comment."""
        return f"(* {text} *)"

    def create_include(self, library: str) -> str:
        """Create OCaml module open statement."""
        return f"open {library}"

    def _map_python_type_to_ocaml(self, python_type: str) -> str:
        """Map Python type to OCaml type."""
        type_mapping = {
            "int": "int",
            "float": "float",
            "str": "string",
            "string": "string",
            "bool": "bool",
            "list": "list",
            "dict": "map",
            "set": "set",
            "None": "unit",
            "Any": "'a",
        }
        return type_mapping.get(python_type, python_type)

    def get_file_extension(self) -> str:
        """Get the file extension for OCaml files."""
        return ".ml"

    def get_runtime_files(self) -> list[str]:
        """Get the list of runtime files needed for OCaml."""
        return ["multigen_runtime.ml"]

__init__(preferences=None)

Initialize the OCaml factory with preferences.

Source code in src/multigen/backends/ocaml/factory.py
def __init__(self, preferences: Optional[BackendPreferences] = None):
    """Initialize the OCaml factory with preferences."""
    self.preferences = preferences or OCamlPreferences()

create_comment(text)

Create OCaml comment.

Source code in src/multigen/backends/ocaml/factory.py
def create_comment(self, text: str) -> str:
    """Create OCaml comment."""
    return f"(* {text} *)"

create_function_signature(name, params, return_type)

Create OCaml function signature.

Source code in src/multigen/backends/ocaml/factory.py
def create_function_signature(self, name: str, params: list[tuple[str, str]], return_type: str) -> str:
    """Create OCaml function signature."""
    ocaml_return_type = self._map_python_type_to_ocaml(return_type)

    if not params:
        return f"let {name} () : {ocaml_return_type}"

    param_types = []
    for _param_name, param_type in params:
        ocaml_param_type = self._map_python_type_to_ocaml(param_type)
        param_types.append(ocaml_param_type)

    type_signature = " -> ".join(param_types + [ocaml_return_type])
    param_names = " ".join(param_name for param_name, _ in params)

    return f"let {name} {param_names} : {type_signature}"

create_include(library)

Create OCaml module open statement.

Source code in src/multigen/backends/ocaml/factory.py
def create_include(self, library: str) -> str:
    """Create OCaml module open statement."""
    return f"open {library}"

create_variable(name, type_name, value=None)

Create OCaml variable declaration.

Source code in src/multigen/backends/ocaml/factory.py
def create_variable(self, name: str, type_name: str, value: Optional[str] = None) -> str:
    """Create OCaml variable declaration."""
    ocaml_type = self._map_python_type_to_ocaml(type_name)
    if value:
        return f"let {name} : {ocaml_type} = {value}"
    else:
        return f"let {name} : {ocaml_type}"

get_file_extension()

Get the file extension for OCaml files.

Source code in src/multigen/backends/ocaml/factory.py
def get_file_extension(self) -> str:
    """Get the file extension for OCaml files."""
    return ".ml"

get_runtime_files()

Get the list of runtime files needed for OCaml.

Source code in src/multigen/backends/ocaml/factory.py
def get_runtime_files(self) -> list[str]:
    """Get the list of runtime files needed for OCaml."""
    return ["multigen_runtime.ml"]

LLVM Backend

multigen.backends.llvm

LLVM IR backend for MultiGen.

This backend converts MultiGen's Static Python IR to LLVM IR using llvmlite, enabling compilation to native binaries, WebAssembly, and other LLVM targets.

IRToLLVMConverter

Bases: IRVisitor

Convert MultiGen Static IR to LLVM IR using the visitor pattern.

Source code in src/multigen/backends/llvm/ir_to_llvm.py
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
class IRToLLVMConverter(IRVisitor):
    """Convert MultiGen Static IR to LLVM IR using the visitor pattern."""

    def __init__(self) -> None:
        """Initialize the LLVM IR converter."""
        self.module: ir.Module = ir.Module(name="multigen_module")
        # Set target triple to empty string to use native target
        # llvmlite will use the host's target triple
        self.module.triple = ""
        self.builder: Optional[ir.IRBuilder] = None
        self.func_symtab: dict[str, ir.Function] = {}
        self.var_symtab: dict[str, ir.AllocaInstr] = {}
        self.global_symtab: dict[str, ir.GlobalVariable] = {}
        self.current_function: Optional[ir.Function] = None
        # Track current loop blocks for break/continue
        self.loop_exit_stack: list[ir.Block] = []
        self.loop_continue_stack: list[ir.Block] = []
        # Runtime declarations for C library
        self.runtime = LLVMRuntimeDeclarations(self.module)

    def visit_module(self, node: IRModule) -> ir.Module:
        """Convert IR module to LLVM module.

        Args:
            node: IR module to convert

        Returns:
            LLVM module with generated functions
        """
        # Declare runtime library functions first
        self.runtime.declare_all()

        # Generate type declarations first (for structs, etc.)
        for type_decl in node.type_declarations:
            type_decl.accept(self)

        # Generate global variables
        for global_var in node.global_variables:
            global_var.accept(self)

        # Generate functions
        for func in node.functions:
            func.accept(self)

        return self.module

    def visit_function(self, node: IRFunction) -> ir.Function:
        """Convert IR function to LLVM function.

        Args:
            node: IR function to convert

        Returns:
            LLVM function with generated body
        """
        # Map IRType → llvmlite type
        ret_type = self._convert_type(node.return_type)
        param_types = [self._convert_type(p.ir_type) for p in node.parameters]

        # Create LLVM function type and function
        func_type = ir.FunctionType(ret_type, param_types)
        func = ir.Function(self.module, func_type, node.name)

        # Store in symbol table
        self.func_symtab[node.name] = func
        self.current_function = func

        # Create entry block
        entry_block = func.append_basic_block(name="entry")
        self.builder = ir.IRBuilder(entry_block)

        # Clear variable symbol table for new function
        self.var_symtab = {}

        # Map parameters to LLVM function arguments
        for i, param in enumerate(node.parameters):
            # Allocate stack space for parameter (enables taking address)
            param_ptr = self.builder.alloca(func.args[i].type, name=param.name)
            self.builder.store(func.args[i], param_ptr)
            self.var_symtab[param.name] = param_ptr

        # Generate function body
        for stmt in node.body:
            stmt.accept(self)

        # Add implicit return if missing
        if not self.builder.block.is_terminated:
            if ret_type == ir.VoidType():
                self.builder.ret_void()
            else:
                # Return zero/null as default
                self.builder.ret(ir.Constant(ret_type, 0))

        return func

    def visit_variable(self, node: IRVariable) -> Union[ir.AllocaInstr, ir.GlobalVariable]:
        """Visit a variable node (used for declarations).

        Args:
            node: IR variable to convert

        Returns:
            LLVM alloca instruction for local variables or GlobalVariable for globals
        """
        var_type = self._convert_type(node.ir_type)

        # If builder is None, this is a global variable
        if self.builder is None:
            # Create global variable with initializer
            if node.initial_value is not None:
                initial_value = node.initial_value.accept(self)
            else:
                # Default initialization to zero
                if var_type == ir.IntType(64):
                    initial_value = ir.Constant(ir.IntType(64), 0)
                elif var_type == ir.DoubleType():
                    initial_value = ir.Constant(ir.DoubleType(), 0.0)
                elif var_type == ir.IntType(1):
                    initial_value = ir.Constant(ir.IntType(1), 0)
                else:
                    raise NotImplementedError(f"Default initialization for type {var_type} not implemented")

            global_var = ir.GlobalVariable(self.module, var_type, name=node.name)
            global_var.initializer = initial_value
            global_var.linkage = "internal"  # Make it module-private
            self.global_symtab[node.name] = global_var
            return global_var
        else:
            # Local variable - use alloca
            var_ptr = self.builder.alloca(var_type, name=node.name)
            self.var_symtab[node.name] = var_ptr
            return var_ptr

    def visit_assignment(self, node: IRAssignment) -> None:
        """Convert IR assignment to LLVM store instruction.

        Args:
            node: IR assignment to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Generate value expression first (we might need its type)
        value = None
        if node.value:
            value = node.value.accept(self)

        # Check if it's a global variable first
        if node.target.name in self.global_symtab:
            var_ptr = self.global_symtab[node.target.name]
        elif node.target.name not in self.var_symtab:
            # Allocate new local variable using IR type
            # For lists, we store pointers in variables, so we alloca space for a pointer
            var_type = self._convert_type(node.target.ir_type)
            var_ptr = self.builder.alloca(var_type, name=node.target.name)
            self.var_symtab[node.target.name] = var_ptr
        else:
            var_ptr = self.var_symtab[node.target.name]

        # Store the value if present
        if value is not None:
            # Check for list dimensionality mismatch (1D vs 2D list literals only)
            value_type_str = str(value.type)
            var_type_str = str(var_ptr.type)

            # Detect actual dimension mismatch:
            # - value is vec_int* (1D) but variable expects vec_vec_int* (2D)
            # - value is vec_vec_int* (2D) but variable expects vec_int* (1D)
            value_is_1d = "vec_int" in value_type_str and "vec_vec_int" not in value_type_str
            value_is_2d = "vec_vec_int" in value_type_str
            var_is_1d = "vec_int" in var_type_str and "vec_vec_int" not in var_type_str
            var_is_2d = "vec_vec_int" in var_type_str

            is_dimension_mismatch = (value_is_1d and var_is_2d) or (value_is_2d and var_is_1d)

            if is_dimension_mismatch:
                # Don't store the mismatched value - it's an empty list with wrong dimension
                # The variable will remain uninitialized, which is fine - it will be initialized
                # on first use (e.g., append)
                pass
            else:
                # Types match - normal store
                self.builder.store(value, var_ptr)
        else:
            # No value provided - initialize pointer types to NULL
            # This handles cases like `result: dict = {}` where we defer the literal
            var_type_str = str(var_ptr.type)
            if "map_" in var_type_str or "vec_" in var_type_str or "set_" in var_type_str:
                # Initialize pointer to NULL (0)
                pointee_type = var_ptr.type.pointee
                null_value = ir.Constant(pointee_type, None)
                self.builder.store(null_value, var_ptr)

    def visit_binary_operation(self, node: IRBinaryOperation) -> ir.Instruction:
        """Convert IR binary operation to LLVM instruction.

        Args:
            node: IR binary operation to convert

        Returns:
            LLVM instruction representing the operation
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # For short-circuit operators (and, or), we need special handling
        # to avoid evaluating the right side when not necessary
        if node.result_type.base_type == IRDataType.BOOL and node.operator in ("and", "or"):
            return self._visit_short_circuit_boolean(node)

        # For all other operators, evaluate both sides immediately
        left = node.left.accept(self)
        right = node.right.accept(self)

        # String operations
        if node.left.result_type.base_type == IRDataType.STRING:
            if node.operator == "+":
                # String concatenation using C library functions
                return self._concat_strings(left, right)

        # Integer operations
        if node.result_type.base_type == IRDataType.INT:
            if node.operator == "+":
                return self.builder.add(left, right, name="add_tmp")
            elif node.operator == "-":
                return self.builder.sub(left, right, name="sub_tmp")
            elif node.operator == "*":
                return self.builder.mul(left, right, name="mul_tmp")
            elif node.operator == "/" or node.operator == "//":
                return self.builder.sdiv(left, right, name="div_tmp")
            elif node.operator == "%":
                # Python modulo uses floored division, C uses truncated division
                # To convert: if remainder and divisor have different signs, add divisor to remainder
                c_rem = self.builder.srem(left, right, name="c_rem")

                # Check if signs differ: (c_rem < 0) != (right < 0)
                zero = ir.Constant(ir.IntType(64), 0)
                rem_neg = self.builder.icmp_signed("<", c_rem, zero, name="rem_neg")
                divisor_neg = self.builder.icmp_signed("<", right, zero, name="divisor_neg")
                signs_differ = self.builder.xor(rem_neg, divisor_neg, name="signs_differ")

                # Check if remainder is non-zero
                rem_nonzero = self.builder.icmp_signed("!=", c_rem, zero, name="rem_nonzero")

                # Adjust if signs differ AND remainder is non-zero
                need_adjust = self.builder.and_(signs_differ, rem_nonzero, name="need_adjust")

                # result = need_adjust ? (c_rem + right) : c_rem
                adjusted = self.builder.add(c_rem, right, name="adjusted")
                result = self.builder.select(need_adjust, adjusted, c_rem, name="mod_tmp")
                return result
            elif node.operator == "<<":
                return self.builder.shl(left, right, name="shl_tmp")
            elif node.operator == ">>":
                return self.builder.ashr(left, right, name="shr_tmp")
            elif node.operator == "&":
                return self.builder.and_(left, right, name="and_tmp")
            elif node.operator == "|":
                return self.builder.or_(left, right, name="or_tmp")
            elif node.operator == "^":
                return self.builder.xor(left, right, name="xor_tmp")

        # Float operations
        elif node.result_type.base_type == IRDataType.FLOAT:
            if node.operator == "+":
                return self.builder.fadd(left, right, name="fadd_tmp")
            elif node.operator == "-":
                return self.builder.fsub(left, right, name="fsub_tmp")
            elif node.operator == "*":
                return self.builder.fmul(left, right, name="fmul_tmp")
            elif node.operator == "/":
                return self.builder.fdiv(left, right, name="fdiv_tmp")

        # Boolean operations (comparisons)
        elif node.result_type.base_type == IRDataType.BOOL:
            # Determine operand types to choose correct comparison instruction
            left_type = node.left.result_type.base_type

            if left_type == IRDataType.INT:
                # Integer comparisons (icmp)
                if node.operator == "<":
                    return self.builder.icmp_signed("<", left, right, name="cmp_tmp")
                elif node.operator == "<=":
                    return self.builder.icmp_signed("<=", left, right, name="cmp_tmp")
                elif node.operator == ">":
                    return self.builder.icmp_signed(">", left, right, name="cmp_tmp")
                elif node.operator == ">=":
                    return self.builder.icmp_signed(">=", left, right, name="cmp_tmp")
                elif node.operator == "==":
                    return self.builder.icmp_signed("==", left, right, name="cmp_tmp")
                elif node.operator == "!=":
                    return self.builder.icmp_signed("!=", left, right, name="cmp_tmp")
            elif left_type == IRDataType.FLOAT:
                # Float comparisons (fcmp)
                if node.operator == "<":
                    return self.builder.fcmp_ordered("<", left, right, name="fcmp_tmp")
                elif node.operator == "<=":
                    return self.builder.fcmp_ordered("<=", left, right, name="fcmp_tmp")
                elif node.operator == ">":
                    return self.builder.fcmp_ordered(">", left, right, name="fcmp_tmp")
                elif node.operator == ">=":
                    return self.builder.fcmp_ordered(">=", left, right, name="fcmp_tmp")
                elif node.operator == "==":
                    return self.builder.fcmp_ordered("==", left, right, name="fcmp_tmp")
                elif node.operator == "!=":
                    return self.builder.fcmp_ordered("!=", left, right, name="fcmp_tmp")
            elif left_type == IRDataType.BOOL:
                # Boolean comparisons (and/or handled separately via _visit_short_circuit_boolean)
                if node.operator == "==":
                    return self.builder.icmp_signed("==", left, right, name="cmp_tmp")
                elif node.operator == "!=":
                    return self.builder.icmp_signed("!=", left, right, name="cmp_tmp")

            # Handle "in" operator for dict membership testing
            # Example: "key" in dict -> map_str_int_contains(dict, key)
            if node.operator == "in":
                # right operand should be the container (dict)
                right_type = node.right.result_type.base_type
                if right_type == IRDataType.DICT:
                    # Determine dict type to select appropriate contains function
                    if (
                        node.right.result_type.element_type
                        and node.right.result_type.element_type.base_type == IRDataType.STRING
                    ):
                        map_contains_func = self.runtime.get_function("map_str_int_contains")
                    else:
                        map_contains_func = self.runtime.get_function("map_int_int_contains")
                    result = self.builder.call(map_contains_func, [right, left], name="contains_result")
                    # Convert i32 result to i1 (bool)
                    zero = ir.Constant(ir.IntType(32), 0)
                    return self.builder.icmp_signed("!=", result, zero, name="contains_bool")

        raise NotImplementedError(
            f"Binary operator '{node.operator}' not implemented for type {node.result_type.base_type}"
        )

    def _visit_short_circuit_boolean(self, node: IRBinaryOperation) -> ir.Instruction:
        """Handle short-circuit evaluation for 'and' and 'or' operators.

        Args:
            node: IR binary operation with 'and' or 'or' operator

        Returns:
            LLVM instruction representing the short-circuit boolean result
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Evaluate left side first
        left = node.left.accept(self)
        left_end_block = self.builder.block  # Block where left evaluation ended

        if node.operator == "and":
            # Short-circuit AND: if left is false, result is false without evaluating right
            eval_right_block = self.current_function.append_basic_block("and.eval_right")
            merge_block = self.current_function.append_basic_block("and.merge")

            # Branch: if left is true, evaluate right; otherwise skip to merge
            self.builder.cbranch(left, eval_right_block, merge_block)

            # Evaluate right side (only if left was true)
            self.builder.position_at_end(eval_right_block)
            right = node.right.accept(self)
            eval_right_end_block = self.builder.block  # May have changed during right evaluation
            self.builder.branch(merge_block)

            # Merge block: use phi to select result
            self.builder.position_at_end(merge_block)
            phi = self.builder.phi(ir.IntType(1), name="and_tmp")
            phi.add_incoming(ir.Constant(ir.IntType(1), 0), left_end_block)  # Left was false
            phi.add_incoming(right, eval_right_end_block)  # Right result
            return phi

        elif node.operator == "or":
            # Short-circuit OR: if left is true, result is true without evaluating right
            eval_right_block = self.current_function.append_basic_block("or.eval_right")
            merge_block = self.current_function.append_basic_block("or.merge")

            # Branch: if left is false, evaluate right; otherwise skip to merge
            self.builder.cbranch(left, merge_block, eval_right_block)

            # Evaluate right side (only if left was false)
            self.builder.position_at_end(eval_right_block)
            right = node.right.accept(self)
            eval_right_end_block = self.builder.block  # May have changed during right evaluation
            self.builder.branch(merge_block)

            # Merge block: use phi to select result
            self.builder.position_at_end(merge_block)
            phi = self.builder.phi(ir.IntType(1), name="or_tmp")
            phi.add_incoming(ir.Constant(ir.IntType(1), 1), left_end_block)  # Left was true
            phi.add_incoming(right, eval_right_end_block)  # Right result
            return phi

        else:
            raise RuntimeError(f"Unexpected operator in short-circuit: {node.operator}")

    def visit_literal(self, node: IRLiteral) -> Union[ir.Constant, ir.CallInstr]:
        """Convert IR literal to LLVM constant or initialization call.

        Args:
            node: IR literal to convert

        Returns:
            LLVM constant value or call instruction for complex types
        """
        llvm_type = self._convert_type(node.result_type)

        if node.result_type.base_type == IRDataType.LIST:
            # List literal - allocate and initialize
            if self.builder is None:
                raise RuntimeError("Builder not initialized - must be inside a function")

            # Determine element type from IR type annotation
            elem_type = None
            if hasattr(node.result_type, "element_type") and node.result_type.element_type:
                elem_type = node.result_type.element_type.base_type
            elif isinstance(node.value, list) and len(node.value) > 0:
                # Fallback: infer from first element
                first_elem = node.value[0]
                if hasattr(first_elem, "result_type"):
                    elem_type = first_elem.result_type.base_type

            # Select appropriate vec_* type based on element type
            is_2d_list = elem_type == IRDataType.LIST

            if elem_type == IRDataType.LIST:
                # 2D list: vec_vec_int
                vec_type = self.runtime.get_vec_vec_int_type()
                vec_init_ptr_func = self.runtime.get_function("vec_vec_int_init_ptr")
                vec_push_func = self.runtime.get_function("vec_vec_int_push")
            elif elem_type == IRDataType.STRING:
                # String list: vec_str
                vec_type = self.runtime.get_vec_str_type()
                vec_init_ptr_func = self.runtime.get_function("vec_str_init_ptr")
                vec_push_func = self.runtime.get_function("vec_str_push")
            else:
                # Default to integer list: vec_int
                vec_type = self.runtime.get_vec_int_type()
                vec_init_ptr_func = self.runtime.get_function("vec_int_init_ptr")
                vec_push_func = self.runtime.get_function("vec_int_push")

            # Allocate space for the vec struct on heap (not stack!)
            # Calculate size of struct using GEP null trick
            i64 = ir.IntType(64)
            i8_ptr = ir.IntType(8).as_pointer()
            null_ptr = ir.Constant(vec_type.as_pointer(), None)
            size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
            struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")

            # Get malloc function and allocate memory
            malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
            raw_ptr = self.builder.call(malloc_func, [struct_size], name="list_malloc")

            # Cast i8* to struct pointer
            vec_ptr = self.builder.bitcast(raw_ptr, vec_type.as_pointer(), name="list_tmp")

            # Initialize it by calling vec_init_ptr() which takes a pointer
            self.builder.call(vec_init_ptr_func, [vec_ptr], name="")

            # If list has elements, push them
            if isinstance(node.value, list) and len(node.value) > 0:
                for element_expr in node.value:
                    # Visit the element expression to get its LLVM value
                    element_val = element_expr.accept(self)

                    if is_2d_list:
                        # For 2D lists, element_val is a pointer to vec_int
                        # vec_vec_int_push now takes vec_int by pointer (not by value)
                        self.builder.call(vec_push_func, [vec_ptr, element_val], name="")
                    else:
                        # For 1D lists, element_val is an i64
                        self.builder.call(vec_push_func, [vec_ptr, element_val], name="")

            # Return the pointer
            return vec_ptr

        elif node.result_type.base_type == IRDataType.DICT:
            # Dict literal - allocate and initialize
            if self.builder is None:
                raise RuntimeError("Builder not initialized - must be inside a function")

            # Check element_type to determine key type (default to int)
            if node.result_type.element_type and node.result_type.element_type.base_type == IRDataType.STRING:
                map_type = self.runtime.get_map_str_int_type()
                map_init_ptr_func = self.runtime.get_function("map_str_int_init_ptr")
            else:
                map_type = self.runtime.get_map_int_int_type()
                map_init_ptr_func = self.runtime.get_function("map_int_int_init_ptr")

            # Allocate space for the map struct on heap
            i64 = ir.IntType(64)
            i8_ptr = ir.IntType(8).as_pointer()
            null_ptr = ir.Constant(map_type.as_pointer(), None)
            size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
            struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")

            # Get malloc function and allocate memory
            malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
            raw_ptr = self.builder.call(malloc_func, [struct_size], name="dict_malloc")

            # Cast i8* to struct pointer
            map_ptr = self.builder.bitcast(raw_ptr, map_type.as_pointer(), name="dict_tmp")

            # Initialize it by calling map_init_ptr()
            self.builder.call(map_init_ptr_func, [map_ptr], name="")

            # TODO: If dict has initial key-value pairs, set them here
            # For now, we only support empty dict literals: {}

            # Return the pointer
            return map_ptr

        elif node.result_type.base_type == IRDataType.SET:
            # Set literal - allocate and initialize (typically empty set from set())
            if self.builder is None:
                raise RuntimeError("Builder not initialized - must be inside a function")

            # Get set_int type and init function
            set_type = self.runtime.get_set_int_type()
            set_init_ptr_func = self.runtime.get_function("set_int_init_ptr")

            # Allocate space for the set struct on heap
            i64 = ir.IntType(64)
            i8_ptr = ir.IntType(8).as_pointer()
            null_ptr = ir.Constant(set_type.as_pointer(), None)
            size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
            struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")

            # Get malloc function and allocate memory
            malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
            raw_ptr = self.builder.call(malloc_func, [struct_size], name="set_malloc")

            # Cast i8* to struct pointer
            set_ptr = self.builder.bitcast(raw_ptr, set_type.as_pointer(), name="set_tmp")

            # Initialize it by calling set_init_ptr()
            self.builder.call(set_init_ptr_func, [set_ptr], name="")

            # Return the pointer
            return set_ptr

        elif node.result_type.base_type == IRDataType.INT:
            return ir.Constant(llvm_type, int(node.value))
        elif node.result_type.base_type == IRDataType.FLOAT:
            return ir.Constant(llvm_type, float(node.value))
        elif node.result_type.base_type == IRDataType.BOOL:
            return ir.Constant(llvm_type, 1 if node.value else 0)
        elif node.result_type.base_type == IRDataType.STRING:
            # String literals are stored as global constants
            # Create a null-terminated string
            str_value = str(node.value)
            str_bytes = (str_value + "\0").encode("utf-8")
            str_const = ir.Constant(ir.ArrayType(ir.IntType(8), len(str_bytes)), bytearray(str_bytes))

            # Create global variable for the string
            str_global = ir.GlobalVariable(self.module, str_const.type, name=f"str_{len(self.module.globals)}")
            str_global.linkage = "internal"
            str_global.global_constant = True
            str_global.initializer = str_const

            # Return pointer to the string (i8*)
            if self.builder is not None:
                return self.builder.gep(str_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])
            else:
                # During global variable initialization, return the global itself
                return str_global
        elif node.result_type.base_type == IRDataType.VOID:
            # VOID literals shouldn't exist - this is likely a bug in IR generation
            # Return null pointer as workaround
            return ir.Constant(ir.IntType(8).as_pointer(), None)

        raise NotImplementedError(f"Literal type {node.result_type.base_type} not implemented (value={node.value})")

    def visit_comprehension(self, node: IRComprehension) -> ir.Value:
        """Convert IR comprehension to LLVM loop with append/insert operations.

        Handles comprehensions like:
        - List: [expr for var in iterable if condition]
        - Dict: {key: value for var in iterable if condition}
        - Set: {expr for var in iterable if condition}

        Generates:
        1. Allocate result container
        2. Generate for loop
        3. Add conditional if present
        4. Append/insert expression to result
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        ast_node = node.ast_node

        # Determine comprehension type
        if isinstance(ast_node, ast.ListComp):
            return self._visit_list_comprehension(node, ast_node)
        elif isinstance(ast_node, ast.DictComp):
            return self._visit_dict_comprehension(node, ast_node)
        elif isinstance(ast_node, ast.SetComp):
            return self._visit_set_comprehension(node, ast_node)
        else:
            raise NotImplementedError(f"Unsupported comprehension type: {type(ast_node).__name__}")

    def _visit_list_comprehension(self, node: IRComprehension, ast_node: ast.ListComp) -> ir.Value:
        """Handle list comprehension: [expr for var in iterable if condition]."""
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized")

        # Allocate result list on heap
        vec_int_type = self.runtime.get_vec_int_type()
        vec_int_init_ptr_func = self.runtime.get_function("vec_int_init_ptr")
        vec_int_push_func = self.runtime.get_function("vec_int_push")

        # Calculate size and malloc the struct
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()
        null_ptr = ir.Constant(vec_int_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="comp_malloc")
        result_ptr = self.builder.bitcast(raw_ptr, vec_int_type.as_pointer(), name="comp_result")

        self.builder.call(vec_int_init_ptr_func, [result_ptr], name="")

        # Process the comprehension (only single generator supported for now)
        if len(ast_node.generators) != 1:
            raise NotImplementedError("Only single generator in comprehensions supported")

        generator = ast_node.generators[0]

        # Determine iteration type: range() or list iteration
        is_range_iter = (
            isinstance(generator.iter, ast.Call)
            and isinstance(generator.iter.func, ast.Name)
            and generator.iter.func.id == "range"
        )

        if is_range_iter:
            # Handle range() iteration
            # At this point we know generator.iter is ast.Call due to is_range_iter check
            assert isinstance(generator.iter, ast.Call)  # For mypy
            range_args = generator.iter.args
            if len(range_args) == 1:
                start_val = ir.Constant(ir.IntType(64), 0)
                end_expr = self._convert_ast_expr(range_args[0])
                step_val = ir.Constant(ir.IntType(64), 1)
            elif len(range_args) == 2:
                start_expr = self._convert_ast_expr(range_args[0])
                end_expr = self._convert_ast_expr(range_args[1])
                start_val = start_expr
                step_val = ir.Constant(ir.IntType(64), 1)
            elif len(range_args) == 3:
                start_expr = self._convert_ast_expr(range_args[0])
                end_expr = self._convert_ast_expr(range_args[1])
                step_expr = self._convert_ast_expr(range_args[2])
                start_val = start_expr
                step_val = step_expr
            else:
                raise ValueError("Invalid range() arguments")

            # Create loop variable for range iteration
            loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
            loop_var = self.builder.alloca(ir.IntType(64), name=loop_var_name)
            self.builder.store(start_val, loop_var)

            # Create loop blocks
            loop_cond_block = self.current_function.append_basic_block(name="loop_cond")
            loop_body_block = self.current_function.append_basic_block(name="loop_body")
            loop_end_block = self.current_function.append_basic_block(name="loop_end")

            # Branch to loop condition
            self.builder.branch(loop_cond_block)

            # Loop condition: i < end
            self.builder.position_at_end(loop_cond_block)
            loop_var_val = self.builder.load(loop_var, name=f"{loop_var_name}_val")
            cond = self.builder.icmp_signed("<", loop_var_val, end_expr, name="loop_cond")
            self.builder.cbranch(cond, loop_body_block, loop_end_block)

            # Loop body
            self.builder.position_at_end(loop_body_block)

            # Store loop variable in symbol table
            old_var = self.var_symtab.get(loop_var_name)
            self.var_symtab[loop_var_name] = loop_var

        else:
            # Handle list iteration: for x in some_list
            # Get the list being iterated over
            iter_expr = self._convert_ast_expr(generator.iter)

            # Get list size
            vec_int_size_func = self.runtime.get_function("vec_int_size")
            list_size = self.builder.call(vec_int_size_func, [iter_expr], name="list_size")

            # Create index variable
            idx_var = self.builder.alloca(ir.IntType(64), name="idx")
            self.builder.store(ir.Constant(ir.IntType(64), 0), idx_var)

            # Create element variable for loop target
            loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
            elem_var = self.builder.alloca(ir.IntType(64), name=loop_var_name)

            # Create loop blocks
            loop_cond_block = self.current_function.append_basic_block(name="loop_cond")
            loop_body_block = self.current_function.append_basic_block(name="loop_body")
            loop_end_block = self.current_function.append_basic_block(name="loop_end")

            # Branch to loop condition
            self.builder.branch(loop_cond_block)

            # Loop condition: idx < size
            self.builder.position_at_end(loop_cond_block)
            idx_val = self.builder.load(idx_var, name="idx_val")
            cond = self.builder.icmp_signed("<", idx_val, list_size, name="loop_cond")
            self.builder.cbranch(cond, loop_body_block, loop_end_block)

            # Loop body
            self.builder.position_at_end(loop_body_block)

            # Get element at index: elem = list[idx]
            vec_int_at_func = self.runtime.get_function("vec_int_at")
            elem_val = self.builder.call(vec_int_at_func, [iter_expr, idx_val], name="elem")
            self.builder.store(elem_val, elem_var)

            # Store element variable in symbol table
            old_var = self.var_symtab.get(loop_var_name)
            self.var_symtab[loop_var_name] = elem_var

            # Store current index and list size for increment later
            start_val = None  # Not used for list iteration
            step_val = ir.Constant(ir.IntType(64), 1)
            loop_var = idx_var  # Use idx_var for increment
            loop_var_val = idx_val

        # Handle optional condition (common for both range and list iteration)
        if generator.ifs:
            if_cond_expr = self._convert_ast_expr(generator.ifs[0])
            if_then_block = self.current_function.append_basic_block(name="if_then")
            if_merge_block = self.current_function.append_basic_block(name="if_merge")

            self.builder.cbranch(if_cond_expr, if_then_block, if_merge_block)

            self.builder.position_at_end(if_then_block)
            # Evaluate and append expression
            expr_val = self._convert_ast_expr(ast_node.elt)
            self.builder.call(vec_int_push_func, [result_ptr, expr_val], name="")
            self.builder.branch(if_merge_block)

            self.builder.position_at_end(if_merge_block)
        else:
            # No condition - just append
            expr_val = self._convert_ast_expr(ast_node.elt)
            self.builder.call(vec_int_push_func, [result_ptr, expr_val], name="")

        # Increment loop variable
        incremented = self.builder.add(loop_var_val, step_val, name="inc")
        self.builder.store(incremented, loop_var)
        self.builder.branch(loop_cond_block)

        # Restore symbol table
        loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
        if old_var is not None:
            self.var_symtab[loop_var_name] = old_var
        else:
            self.var_symtab.pop(loop_var_name, None)

        # Continue after loop
        self.builder.position_at_end(loop_end_block)

        return result_ptr

    def _infer_dict_key_type(self, key_expr: ast.expr) -> str:
        """Infer the type of a dict key expression.

        Returns:
            "int" for integer keys, "str" for string keys
        """
        # Integer keys: constants, variables, arithmetic operations
        if isinstance(key_expr, ast.Constant):
            if isinstance(key_expr.value, int):
                return "int"
            elif isinstance(key_expr.value, str):
                return "str"
        elif isinstance(key_expr, (ast.Name, ast.BinOp, ast.UnaryOp)):
            # Assume Name and arithmetic ops are integers
            return "int"
        elif isinstance(key_expr, ast.Call):
            # String operations like str()
            if isinstance(key_expr.func, ast.Name) and key_expr.func.id == "str":
                return "str"
            return "int"

        # Default to string for safety
        return "str"

    def _generate_dict_values_vec(self, dict_ptr: ir.Value) -> ir.Value:
        """Generate a vec_int containing all values from a dict.

        This is used to implement dict.values() which returns an iterable.

        Args:
            dict_ptr: Pointer to the dict (map_int_int*)

        Returns:
            Pointer to vec_int containing all values
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized")

        # Allocate vec_int on heap
        vec_type = self.runtime.get_vec_int_type()
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()

        # Malloc the vec_int struct
        null_ptr = ir.Constant(vec_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="vec_size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="vec_struct_size")
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="values_vec_malloc")
        vec_ptr = self.builder.bitcast(raw_ptr, vec_type.as_pointer(), name="values_vec")

        # Initialize the vec
        vec_init_func = self.runtime.get_function("vec_int_init_ptr")
        self.builder.call(vec_init_func, [vec_ptr], name="")

        # Get dict runtime functions (assume int-keyed for now)
        capacity_func = self.runtime.get_function("map_int_int_capacity")
        is_occupied_func = self.runtime.get_function("map_int_int_entry_is_occupied")
        entry_value_func = self.runtime.get_function("map_int_int_entry_value")
        vec_push_func = self.runtime.get_function("vec_int_push")

        # Get dict capacity
        capacity = self.builder.call(capacity_func, [dict_ptr], name="dict_capacity")

        # Create loop to iterate through all entries
        loop_var = self.builder.alloca(i64, name="values_iter_idx")
        self.builder.store(ir.Constant(i64, 0), loop_var)

        loop_cond_block = self.current_function.append_basic_block(name="values_loop_cond")
        loop_body_block = self.current_function.append_basic_block(name="values_loop_body")
        entry_check_block = self.current_function.append_basic_block(name="values_entry_check")
        loop_inc_block = self.current_function.append_basic_block(name="values_loop_inc")
        loop_end_block = self.current_function.append_basic_block(name="values_loop_end")

        # Branch to condition
        self.builder.branch(loop_cond_block)

        # Loop condition: idx < capacity
        self.builder.position_at_end(loop_cond_block)
        idx_val = self.builder.load(loop_var, name="idx_val")
        cond = self.builder.icmp_signed("<", idx_val, capacity, name="values_cond")
        self.builder.cbranch(cond, loop_body_block, loop_end_block)

        # Loop body: check if occupied
        self.builder.position_at_end(loop_body_block)
        is_occupied = self.builder.call(is_occupied_func, [dict_ptr, idx_val], name="is_occupied")
        zero_i32 = ir.Constant(ir.IntType(32), 0)
        occupied_cond = self.builder.icmp_signed("!=", is_occupied, zero_i32, name="occupied_cond")
        self.builder.cbranch(occupied_cond, entry_check_block, loop_inc_block)

        # Entry is occupied - extract value and push to vec
        self.builder.position_at_end(entry_check_block)
        entry_value = self.builder.call(entry_value_func, [dict_ptr, idx_val], name="entry_value")
        self.builder.call(vec_push_func, [vec_ptr, entry_value], name="")
        self.builder.branch(loop_inc_block)

        # Increment index
        self.builder.position_at_end(loop_inc_block)
        incremented = self.builder.add(idx_val, ir.Constant(i64, 1), name="idx_inc")
        self.builder.store(incremented, loop_var)
        self.builder.branch(loop_cond_block)

        # After loop, return the vec pointer
        self.builder.position_at_end(loop_end_block)

        return vec_ptr

    def _visit_dict_comprehension_items(
        self,
        ast_node: ast.DictComp,
        generator: ast.comprehension,
        result_ptr: ir.Value,
        map_set_func: ir.Function,
        key_type: str,
    ) -> ir.Value:
        """Handle dict comprehension with .items() iteration.

        Example: {k: v for k, v in source_dict.items() if condition}
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized")

        # Get the source dict being iterated over
        # generator.iter is ast.Call to .items()
        # generator.iter.func is ast.Attribute with .items
        # generator.iter.func.value is the dict variable
        assert isinstance(generator.iter, ast.Call)
        assert isinstance(generator.iter.func, ast.Attribute)
        source_dict_ast = generator.iter.func.value
        source_dict_ptr = self._convert_ast_expr(source_dict_ast)

        # Determine source dict type (map_int_int or map_str_int)
        # For now, assume both source and result use same type based on key_type
        if key_type == "int":
            capacity_func = self.runtime.get_function("map_int_int_capacity")
            is_occupied_func = self.runtime.get_function("map_int_int_entry_is_occupied")
            entry_key_func = self.runtime.get_function("map_int_int_entry_key")
            entry_value_func = self.runtime.get_function("map_int_int_entry_value")
        else:
            raise NotImplementedError("String-keyed dict .items() iteration not yet implemented")

        # Get capacity of source dict
        capacity = self.builder.call(capacity_func, [source_dict_ptr], name="source_capacity")

        # Create loop variable for iterating through capacity
        i64 = ir.IntType(64)
        loop_var = self.builder.alloca(i64, name="items_iter_idx")
        self.builder.store(ir.Constant(i64, 0), loop_var)

        # Create loop blocks
        loop_cond_block = self.current_function.append_basic_block(name="items_loop_cond")
        loop_body_block = self.current_function.append_basic_block(name="items_loop_body")
        entry_check_block = self.current_function.append_basic_block(name="items_entry_check")
        loop_increment_block = self.current_function.append_basic_block(name="items_loop_inc")
        loop_end_block = self.current_function.append_basic_block(name="items_loop_end")

        # Branch to loop condition
        self.builder.branch(loop_cond_block)

        # Loop condition: idx < capacity
        self.builder.position_at_end(loop_cond_block)
        idx_val = self.builder.load(loop_var, name="idx_val")
        cond = self.builder.icmp_signed("<", idx_val, capacity, name="items_cond")
        self.builder.cbranch(cond, loop_body_block, loop_end_block)

        # Loop body: check if entry is occupied
        self.builder.position_at_end(loop_body_block)
        is_occupied = self.builder.call(is_occupied_func, [source_dict_ptr, idx_val], name="is_occupied")
        zero_i32 = ir.Constant(ir.IntType(32), 0)
        occupied_cond = self.builder.icmp_signed("!=", is_occupied, zero_i32, name="occupied_cond")
        self.builder.cbranch(occupied_cond, entry_check_block, loop_increment_block)

        # Entry is occupied - extract key and value
        self.builder.position_at_end(entry_check_block)
        entry_key = self.builder.call(entry_key_func, [source_dict_ptr, idx_val], name="entry_key")
        entry_value = self.builder.call(entry_value_func, [source_dict_ptr, idx_val], name="entry_value")

        # Handle tuple unpacking for (k, v)
        # generator.target should be ast.Tuple with two elements
        if isinstance(generator.target, ast.Tuple) and len(generator.target.elts) == 2:
            key_var_name = generator.target.elts[0].id if isinstance(generator.target.elts[0], ast.Name) else "k"
            val_var_name = generator.target.elts[1].id if isinstance(generator.target.elts[1], ast.Name) else "v"

            # Allocate and store key and value in symbol table
            key_alloca = self.builder.alloca(i64, name=key_var_name)
            val_alloca = self.builder.alloca(i64, name=val_var_name)
            self.builder.store(entry_key, key_alloca)
            self.builder.store(entry_value, val_alloca)

            old_key_var = self.var_symtab.get(key_var_name)
            old_val_var = self.var_symtab.get(val_var_name)
            self.var_symtab[key_var_name] = key_alloca
            self.var_symtab[val_var_name] = val_alloca
        else:
            raise NotImplementedError("Dict .items() requires tuple unpacking: for k, v in ...")

        # Handle optional filter condition
        if generator.ifs:
            filter_cond_expr = self._convert_ast_expr(generator.ifs[0])
            filter_then_block = self.current_function.append_basic_block(name="items_filter_then")

            self.builder.cbranch(filter_cond_expr, filter_then_block, loop_increment_block)
            self.builder.position_at_end(filter_then_block)

            # Evaluate key and value expressions, then insert
            result_key = self._convert_ast_expr(ast_node.key)
            result_value = self._convert_ast_expr(ast_node.value)
            self.builder.call(map_set_func, [result_ptr, result_key, result_value], name="")
            self.builder.branch(loop_increment_block)
        else:
            # No filter - just insert
            result_key = self._convert_ast_expr(ast_node.key)
            result_value = self._convert_ast_expr(ast_node.value)
            self.builder.call(map_set_func, [result_ptr, result_key, result_value], name="")
            self.builder.branch(loop_increment_block)

        # Increment loop variable
        self.builder.position_at_end(loop_increment_block)
        # Restore symbol table for next iteration
        if isinstance(generator.target, ast.Tuple):
            if old_key_var is not None:
                self.var_symtab[key_var_name] = old_key_var
            else:
                self.var_symtab.pop(key_var_name, None)

            if old_val_var is not None:
                self.var_symtab[val_var_name] = old_val_var
            else:
                self.var_symtab.pop(val_var_name, None)

        # Actually increment and loop back
        incremented = self.builder.add(idx_val, ir.Constant(i64, 1), name="idx_inc")
        self.builder.store(incremented, loop_var)
        self.builder.branch(loop_cond_block)

        # Continue after loop
        self.builder.position_at_end(loop_end_block)

        return result_ptr

    def _visit_dict_comprehension(self, node: IRComprehension, ast_node: ast.DictComp) -> ir.Value:
        """Handle dict comprehension: {key_expr: value_expr for var in iterable if condition}."""
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized")

        # Detect key type to select appropriate map runtime
        key_type = self._infer_dict_key_type(ast_node.key)

        if key_type == "int":
            # Use map_int_int for integer keys
            map_type = self.runtime.get_map_int_int_type()
            map_init_ptr_func = self.runtime.get_function("map_int_int_init_ptr")
            map_set_func = self.runtime.get_function("map_int_int_set")
        else:
            # Use map_str_int for string keys
            map_type = self.runtime.get_map_str_int_type()
            map_init_ptr_func = self.runtime.get_function("map_str_int_init_ptr")
            map_set_func = self.runtime.get_function("map_str_int_set")

        # Calculate size and malloc the struct
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()
        null_ptr = ir.Constant(map_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="dict_comp_malloc")
        result_ptr = self.builder.bitcast(raw_ptr, map_type.as_pointer(), name="dict_comp_result")

        self.builder.call(map_init_ptr_func, [result_ptr], name="")

        # Process the comprehension (only single generator supported for now)
        if len(ast_node.generators) != 1:
            raise NotImplementedError("Only single generator in dict comprehensions supported")

        generator = ast_node.generators[0]

        # Determine iteration type: range() or .items()
        is_range_iter = (
            isinstance(generator.iter, ast.Call)
            and isinstance(generator.iter.func, ast.Name)
            and generator.iter.func.id == "range"
        )

        is_items_iter = (
            isinstance(generator.iter, ast.Call)
            and isinstance(generator.iter.func, ast.Attribute)
            and generator.iter.func.attr == "items"
        )

        if is_items_iter:
            # Handle .items() iteration: {k: v for k, v in dict.items()}
            return self._visit_dict_comprehension_items(ast_node, generator, result_ptr, map_set_func, key_type)
        elif not is_range_iter:
            raise NotImplementedError("Dict comprehensions only support range() and .items() iteration")

        # Handle range() iteration
        assert isinstance(generator.iter, ast.Call)
        range_args = generator.iter.args
        if len(range_args) == 1:
            start_val = ir.Constant(ir.IntType(64), 0)
            end_expr = self._convert_ast_expr(range_args[0])
            step_val = ir.Constant(ir.IntType(64), 1)
        elif len(range_args) == 2:
            start_expr = self._convert_ast_expr(range_args[0])
            end_expr = self._convert_ast_expr(range_args[1])
            start_val = start_expr
            step_val = ir.Constant(ir.IntType(64), 1)
        elif len(range_args) == 3:
            start_expr = self._convert_ast_expr(range_args[0])
            end_expr = self._convert_ast_expr(range_args[1])
            step_expr = self._convert_ast_expr(range_args[2])
            start_val = start_expr
            step_val = step_expr
        else:
            raise ValueError("Invalid range() arguments")

        # Create loop variable
        loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
        loop_var = self.builder.alloca(ir.IntType(64), name=loop_var_name)
        self.builder.store(start_val, loop_var)

        # Create loop blocks
        loop_cond_block = self.current_function.append_basic_block(name="dict_loop_cond")
        loop_body_block = self.current_function.append_basic_block(name="dict_loop_body")
        loop_end_block = self.current_function.append_basic_block(name="dict_loop_end")

        # Branch to loop condition
        self.builder.branch(loop_cond_block)

        # Loop condition: i < end
        self.builder.position_at_end(loop_cond_block)
        loop_var_val = self.builder.load(loop_var, name=f"{loop_var_name}_val")
        cond = self.builder.icmp_signed("<", loop_var_val, end_expr, name="dict_loop_cond")
        self.builder.cbranch(cond, loop_body_block, loop_end_block)

        # Loop body
        self.builder.position_at_end(loop_body_block)

        # Store loop variable in symbol table
        old_var = self.var_symtab.get(loop_var_name)
        self.var_symtab[loop_var_name] = loop_var

        # Handle optional condition
        if generator.ifs:
            if_cond_expr = self._convert_ast_expr(generator.ifs[0])
            if_then_block = self.current_function.append_basic_block(name="dict_if_then")
            if_merge_block = self.current_function.append_basic_block(name="dict_if_merge")

            self.builder.cbranch(if_cond_expr, if_then_block, if_merge_block)

            self.builder.position_at_end(if_then_block)
            # Evaluate key and value, then insert
            key_val = self._convert_ast_expr(ast_node.key)
            value_val = self._convert_ast_expr(ast_node.value)
            self.builder.call(map_set_func, [result_ptr, key_val, value_val], name="")
            self.builder.branch(if_merge_block)

            self.builder.position_at_end(if_merge_block)
        else:
            # No condition - just insert
            key_val = self._convert_ast_expr(ast_node.key)
            value_val = self._convert_ast_expr(ast_node.value)
            self.builder.call(map_set_func, [result_ptr, key_val, value_val], name="")

        # Increment loop variable
        incremented = self.builder.add(loop_var_val, step_val, name="dict_inc")
        self.builder.store(incremented, loop_var)
        self.builder.branch(loop_cond_block)

        # Restore symbol table
        if old_var is not None:
            self.var_symtab[loop_var_name] = old_var
        else:
            self.var_symtab.pop(loop_var_name, None)

        # Continue after loop
        self.builder.position_at_end(loop_end_block)

        return result_ptr

    def _visit_set_comprehension(self, node: IRComprehension, ast_node: ast.SetComp) -> ir.Value:
        """Handle set comprehension: {expr for var in iterable if condition}."""
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized")

        # Allocate result set on heap (like list comprehensions)
        set_int_type = self.runtime.get_set_int_type()
        set_int_init_ptr_func = self.runtime.get_function("set_int_init_ptr")
        set_int_insert_func = self.runtime.get_function("set_int_insert")

        # Calculate size and malloc the struct on heap
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()
        null_ptr = ir.Constant(set_int_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="set_size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="set_struct_size")
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="set_malloc")
        result_set = self.builder.bitcast(raw_ptr, set_int_type.as_pointer(), name="comp_set")

        # Initialize the set via pointer (avoids ABI issues with large struct returns)
        self.builder.call(set_int_init_ptr_func, [result_set], name="")

        # Process the comprehension (only single generator supported for now)
        if len(ast_node.generators) != 1:
            raise NotImplementedError("Only single generator in set comprehensions supported")

        generator = ast_node.generators[0]

        # Determine iteration type: range() or set iteration
        is_range_iter = (
            isinstance(generator.iter, ast.Call)
            and isinstance(generator.iter.func, ast.Name)
            and generator.iter.func.id == "range"
        )

        if is_range_iter:
            # Handle range() iteration
            assert isinstance(generator.iter, ast.Call)  # For mypy
            range_args = generator.iter.args
            if len(range_args) == 1:
                start_val = ir.Constant(ir.IntType(64), 0)
                end_expr = self._convert_ast_expr(range_args[0])
                step_val = ir.Constant(ir.IntType(64), 1)
            elif len(range_args) == 2:
                start_expr = self._convert_ast_expr(range_args[0])
                end_expr = self._convert_ast_expr(range_args[1])
                start_val = start_expr
                step_val = ir.Constant(ir.IntType(64), 1)
            elif len(range_args) == 3:
                start_expr = self._convert_ast_expr(range_args[0])
                end_expr = self._convert_ast_expr(range_args[1])
                step_expr = self._convert_ast_expr(range_args[2])
                start_val = start_expr
                step_val = step_expr
            else:
                raise ValueError("Invalid range() arguments")

            # Create loop variable for range iteration
            loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
            loop_var = self.builder.alloca(ir.IntType(64), name=loop_var_name)
            self.builder.store(start_val, loop_var)

            # Create loop blocks
            loop_cond_block = self.current_function.append_basic_block(name="set_loop_cond")
            loop_body_block = self.current_function.append_basic_block(name="set_loop_body")
            loop_end_block = self.current_function.append_basic_block(name="set_loop_end")

            # Branch to loop condition
            self.builder.branch(loop_cond_block)

            # Loop condition: i < end
            self.builder.position_at_end(loop_cond_block)
            loop_var_val = self.builder.load(loop_var, name=f"{loop_var_name}_val")
            cond = self.builder.icmp_signed("<", loop_var_val, end_expr, name="set_loop_cond")
            self.builder.cbranch(cond, loop_body_block, loop_end_block)

            # Loop body
            self.builder.position_at_end(loop_body_block)

            # Store loop variable in symbol table
            old_var = self.var_symtab.get(loop_var_name)
            self.var_symtab[loop_var_name] = loop_var

        else:
            # Handle set/list iteration: for x in some_set
            iter_expr = self._convert_ast_expr(generator.iter)

            # Detect if we're iterating over a set or list based on LLVM type
            is_set_iter = False
            if hasattr(iter_expr, "type") and isinstance(iter_expr.type, ir.PointerType):
                pointee_type_str = str(iter_expr.type.pointee)
                is_set_iter = "set_int" in pointee_type_str

            # Get size and element access functions based on type
            if is_set_iter:
                size_func = self.runtime.get_function("set_int_size")
                get_nth_func = self.runtime.get_function("set_int_get_nth_element")
            else:
                size_func = self.runtime.get_function("vec_int_size")
                get_nth_func = self.runtime.get_function("vec_int_at")

            iter_size = self.builder.call(size_func, [iter_expr], name="iter_size")

            # Create index variable
            idx_var = self.builder.alloca(ir.IntType(64), name="idx")
            self.builder.store(ir.Constant(ir.IntType(64), 0), idx_var)

            # Create element variable for loop target
            loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
            elem_var = self.builder.alloca(ir.IntType(64), name=loop_var_name)

            # Create loop blocks
            loop_cond_block = self.current_function.append_basic_block(name="set_loop_cond")
            loop_body_block = self.current_function.append_basic_block(name="set_loop_body")
            loop_end_block = self.current_function.append_basic_block(name="set_loop_end")

            # Branch to loop condition
            self.builder.branch(loop_cond_block)

            # Loop condition: idx < size
            self.builder.position_at_end(loop_cond_block)
            idx_val = self.builder.load(idx_var, name="idx_val")
            cond = self.builder.icmp_signed("<", idx_val, iter_size, name="set_loop_cond")
            self.builder.cbranch(cond, loop_body_block, loop_end_block)

            # Loop body
            self.builder.position_at_end(loop_body_block)

            # Get element at index: elem = iter[idx] or set_get_nth_element(iter, idx)
            elem_val = self.builder.call(get_nth_func, [iter_expr, idx_val], name="elem")
            self.builder.store(elem_val, elem_var)

            # Store element variable in symbol table
            old_var = self.var_symtab.get(loop_var_name)
            self.var_symtab[loop_var_name] = elem_var

            # Store for increment
            step_val = ir.Constant(ir.IntType(64), 1)
            loop_var = idx_var
            loop_var_val = idx_val

        # Handle optional condition (common for both range and list iteration)
        if generator.ifs:
            if_cond_expr = self._convert_ast_expr(generator.ifs[0])
            if_then_block = self.current_function.append_basic_block(name="set_if_then")
            if_merge_block = self.current_function.append_basic_block(name="set_if_merge")

            self.builder.cbranch(if_cond_expr, if_then_block, if_merge_block)

            self.builder.position_at_end(if_then_block)
            # Evaluate and insert expression
            expr_val = self._convert_ast_expr(ast_node.elt)
            self.builder.call(set_int_insert_func, [result_set, expr_val], name="")
            self.builder.branch(if_merge_block)

            self.builder.position_at_end(if_merge_block)
        else:
            # No condition - just insert
            expr_val = self._convert_ast_expr(ast_node.elt)
            self.builder.call(set_int_insert_func, [result_set, expr_val], name="")

        # Increment loop variable
        incremented = self.builder.add(loop_var_val, step_val, name="inc")
        self.builder.store(incremented, loop_var)
        self.builder.branch(loop_cond_block)

        # Restore symbol table
        loop_var_name = generator.target.id if isinstance(generator.target, ast.Name) else "loop_var"
        if old_var is not None:
            self.var_symtab[loop_var_name] = old_var
        else:
            self.var_symtab.pop(loop_var_name, None)

        # Continue after loop
        self.builder.position_at_end(loop_end_block)

        return result_set

    def _convert_ast_expr(self, ast_expr: ast.expr) -> ir.Value:
        """Helper to convert AST expression to LLVM value."""
        if self.builder is None:
            raise RuntimeError("Builder not initialized")

        if isinstance(ast_expr, ast.Constant):
            # Handle constant values - must be int for our use case
            if not isinstance(ast_expr.value, int):
                raise ValueError(f"Expected int constant, got {type(ast_expr.value)}")
            return ir.Constant(ir.IntType(64), ast_expr.value)
        elif isinstance(ast_expr, ast.Name):
            var_ptr = self.var_symtab[ast_expr.id]
            return self.builder.load(var_ptr, name=ast_expr.id)
        elif isinstance(ast_expr, ast.BinOp):
            left = self._convert_ast_expr(ast_expr.left)
            right = self._convert_ast_expr(ast_expr.right)
            if isinstance(ast_expr.op, ast.Add):
                return self.builder.add(left, right, name="add_tmp")
            elif isinstance(ast_expr.op, ast.Sub):
                return self.builder.sub(left, right, name="sub_tmp")
            elif isinstance(ast_expr.op, ast.Mult):
                return self.builder.mul(left, right, name="mul_tmp")
            elif isinstance(ast_expr.op, ast.Mod):
                return self.builder.srem(left, right, name="mod_tmp")
            else:
                raise NotImplementedError(f"Binary op {type(ast_expr.op).__name__} not implemented")
        elif isinstance(ast_expr, ast.Compare):
            left = self._convert_ast_expr(ast_expr.left)
            right = self._convert_ast_expr(ast_expr.comparators[0])
            op = ast_expr.ops[0]
            if isinstance(op, ast.Lt):
                return self.builder.icmp_signed("<", left, right, name="cmp_tmp")
            elif isinstance(op, ast.Gt):
                return self.builder.icmp_signed(">", left, right, name="cmp_tmp")
            elif isinstance(op, ast.Eq):
                return self.builder.icmp_signed("==", left, right, name="cmp_tmp")
            elif isinstance(op, ast.LtE):
                return self.builder.icmp_signed("<=", left, right, name="cmp_tmp")
            elif isinstance(op, ast.GtE):
                return self.builder.icmp_signed(">=", left, right, name="cmp_tmp")
            else:
                raise NotImplementedError(f"Compare op {type(op).__name__} not implemented")
        else:
            raise NotImplementedError(f"AST expression {type(ast_expr).__name__} not implemented in comprehensions")

    def visit_variable_reference(self, node: IRVariableReference) -> ir.LoadInstr:
        """Convert IR variable reference to LLVM load instruction.

        Args:
            node: IR variable reference to convert

        Returns:
            LLVM load instruction
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Check global variables first, then local
        var_ptr = self.global_symtab.get(node.variable.name)
        if var_ptr is None:
            var_ptr = self.var_symtab.get(node.variable.name)
        if var_ptr is None:
            raise RuntimeError(f"Variable '{node.variable.name}' not found in symbol table")

        return self.builder.load(var_ptr, name=node.variable.name)

    def _get_or_create_c_function(
        self, name: str, ret_type: ir.Type, arg_types: list[ir.Type], var_arg: bool = False
    ) -> ir.Function:
        """Get or create a C library function declaration.

        Args:
            name: Name of the C function
            ret_type: Return type
            arg_types: List of argument types
            var_arg: Whether function has variable arguments

        Returns:
            LLVM function declaration
        """
        if name in self.func_symtab:
            return self.func_symtab[name]

        func_ty = ir.FunctionType(ret_type, arg_types, var_arg=var_arg)
        func = ir.Function(self.module, func_ty, name=name)
        self.func_symtab[name] = func
        return func

    def _create_string_constant(self, str_value: str) -> ir.Value:
        """Create a string constant.

        Args:
            str_value: String value to create

        Returns:
            Pointer to the string constant (i8*)
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized")

        str_bytes = (str_value + "\0").encode("utf-8")
        str_const = ir.Constant(ir.ArrayType(ir.IntType(8), len(str_bytes)), bytearray(str_bytes))

        # Create global variable for the string
        str_global = ir.GlobalVariable(self.module, str_const.type, name=f"str_{len(self.module.globals)}")
        str_global.linkage = "internal"
        str_global.global_constant = True
        str_global.initializer = str_const

        # Return pointer to the string (i8*)
        return self.builder.gep(str_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])

    def _concat_strings(self, left: ir.Value, right: ir.Value) -> ir.Value:
        """Concatenate two strings using C library functions.

        Args:
            left: First string (i8*)
            right: Second string (i8*)

        Returns:
            Concatenated string (i8*)
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized")

        # Declare C library functions if not already declared
        i8_ptr = ir.IntType(8).as_pointer()
        i64 = ir.IntType(64)

        strlen_func = self._get_or_create_c_function("strlen", i64, [i8_ptr])
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        strcpy_func = self._get_or_create_c_function("strcpy", i8_ptr, [i8_ptr, i8_ptr])
        strcat_func = self._get_or_create_c_function("strcat", i8_ptr, [i8_ptr, i8_ptr])

        # Get lengths of both strings
        left_len = self.builder.call(strlen_func, [left], name="left_len")
        right_len = self.builder.call(strlen_func, [right], name="right_len")

        # Calculate total length (left_len + right_len + 1 for null terminator)
        total_len = self.builder.add(left_len, right_len, name="total_len")
        total_len_plus_null = self.builder.add(total_len, ir.Constant(i64, 1), name="total_len_plus_null")

        # Allocate memory for result
        result_ptr = self.builder.call(malloc_func, [total_len_plus_null], name="result_ptr")

        # Copy first string
        self.builder.call(strcpy_func, [result_ptr, left], name="strcpy_tmp")

        # Concatenate second string
        self.builder.call(strcat_func, [result_ptr, right], name="strcat_tmp")

        return result_ptr

    def _get_or_create_builtin(self, name: str, arg_types: list[ir.Type]) -> ir.Function:
        """Get or create a builtin function declaration.

        Args:
            name: Name of the builtin function
            arg_types: List of argument types

        Returns:
            LLVM function declaration for the builtin
        """
        # Check if already declared
        if name in self.func_symtab:
            return self.func_symtab[name]

        # Create builtin function declarations
        if name == "print":
            # print() uses printf internally
            # For simplicity, we'll handle integer printing first
            # Signature: int printf(i8*, ...)
            printf_ty = ir.FunctionType(ir.IntType(32), [ir.IntType(8).as_pointer()], var_arg=True)
            printf_func = ir.Function(self.module, printf_ty, name="printf")
            self.func_symtab["printf"] = printf_func
            return printf_func
        else:
            raise NotImplementedError(f"Builtin function '{name}' not implemented")

    def visit_function_call(self, node: IRFunctionCall) -> ir.CallInstr:
        """Convert IR function call to LLVM call instruction.

        Args:
            node: IR function call to convert

        Returns:
            LLVM call instruction
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Handle method calls (from IR builder)
        if node.function_name == "__method_append__":
            # list.append(value) -> vec_int_push, vec_str_push, or vec_vec_int_push
            if len(node.arguments) != 2:
                raise RuntimeError("append() requires exactly 2 arguments (list and value)")

            list_ptr = node.arguments[0].accept(self)  # Already a pointer
            value = node.arguments[1].accept(self)

            # Determine list type based on LLVM types
            from llvmlite import ir as llvm_ir

            list_ptr_type = list_ptr.type

            # Check the pointee type to determine which vec_* type we have
            if isinstance(list_ptr_type, llvm_ir.PointerType):
                pointee_type_str = str(list_ptr_type.pointee)

                if "vec_vec_int" in pointee_type_str:
                    # 2D list: vec_vec_int_push(list_ptr, vec_int_ptr)
                    vec_push_func = self.runtime.get_function("vec_vec_int_push")
                    self.builder.call(vec_push_func, [list_ptr, value], name="")
                elif "vec_str" in pointee_type_str:
                    # String list: vec_str_push(list_ptr, char* value)
                    vec_push_func = self.runtime.get_function("vec_str_push")
                    self.builder.call(vec_push_func, [list_ptr, value], name="")
                else:
                    # Integer list (default): vec_int_push(list_ptr, int_value)
                    vec_push_func = self.runtime.get_function("vec_int_push")
                    self.builder.call(vec_push_func, [list_ptr, value], name="")
            else:
                # Fallback to vec_int
                vec_push_func = self.runtime.get_function("vec_int_push")
                self.builder.call(vec_push_func, [list_ptr, value], name="")

            # Return the pointer (unchanged, since append mutates in place)
            return list_ptr

        elif node.function_name == "__getitem__":
            # list[index] or dict[key] -> vec_*_at or map_*_get
            if len(node.arguments) != 2:
                raise RuntimeError("__getitem__() requires exactly 2 arguments (container and index/key)")

            container_ptr = node.arguments[0].accept(self)  # Already a pointer
            key_or_index = node.arguments[1].accept(self)

            # Determine container type based on LLVM type
            from llvmlite import ir as llvm_ir

            container_type = container_ptr.type

            # Check the pointee type to determine which container we have
            if isinstance(container_type, llvm_ir.PointerType):
                pointee_type_str = str(container_type.pointee)

                if "map_str_int" in pointee_type_str:
                    # Dict: map_str_int_get(dict_ptr, key) returns i64
                    map_get_func = self.runtime.get_function("map_str_int_get")
                    return self.builder.call(map_get_func, [container_ptr, key_or_index], name="dict_get")
                elif "map_int_int" in pointee_type_str:
                    # Dict: map_int_int_get(dict_ptr, key) returns i64
                    map_get_func = self.runtime.get_function("map_int_int_get")
                    return self.builder.call(map_get_func, [container_ptr, key_or_index], name="dict_get")
                elif "vec_vec_int" in pointee_type_str:
                    # 2D list: vec_vec_int_at(list_ptr, index) returns vec_int*
                    vec_at_func = self.runtime.get_function("vec_vec_int_at")
                    return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")
                elif "vec_str" in pointee_type_str:
                    # String list: vec_str_at(list_ptr, index) returns char*
                    vec_at_func = self.runtime.get_function("vec_str_at")
                    return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")
                else:
                    # Integer list (default): vec_int_at(list_ptr, index) returns i64
                    vec_at_func = self.runtime.get_function("vec_int_at")
                    return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")
            else:
                # Fallback to vec_int
                vec_at_func = self.runtime.get_function("vec_int_at")
                return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")

        elif node.function_name == "__set_get_nth__":
            # set_get_nth_element(set, index) -> element
            if len(node.arguments) != 2:
                raise RuntimeError("__set_get_nth__() requires exactly 2 arguments (set and index)")

            set_ptr = node.arguments[0].accept(self)  # Already a pointer
            index = node.arguments[1].accept(self)

            # Call set_int_get_nth_element(set_ptr, index) returns i64
            get_nth_func = self.runtime.get_function("set_int_get_nth_element")
            return self.builder.call(get_nth_func, [set_ptr, index], name="set_get_nth")

        elif node.function_name == "__setitem__":
            # list[index] = value or dict[key] = value -> vec_*_set or map_*_set
            if len(node.arguments) != 3:
                raise RuntimeError("__setitem__() requires exactly 3 arguments (container, index/key, value)")

            container_ptr = node.arguments[0].accept(self)  # Already a pointer
            key_or_index = node.arguments[1].accept(self)
            value = node.arguments[2].accept(self)

            # Determine container type based on LLVM type
            from llvmlite import ir as llvm_ir

            container_type = container_ptr.type

            # Check the pointee type to determine which container we have
            if isinstance(container_type, llvm_ir.PointerType):
                pointee_type_str = str(container_type.pointee)

                if "map_str_int" in pointee_type_str:
                    # Dict: map_str_int_set(dict_ptr, key, value)
                    map_set_func = self.runtime.get_function("map_str_int_set")
                    return self.builder.call(map_set_func, [container_ptr, key_or_index, value], name="")
                elif "map_int_int" in pointee_type_str:
                    # Dict: map_int_int_set(dict_ptr, key, value)
                    map_set_func = self.runtime.get_function("map_int_int_set")
                    return self.builder.call(map_set_func, [container_ptr, key_or_index, value], name="")
                elif "vec_str" in pointee_type_str:
                    # String list: vec_str_set(list_ptr, index, char* value)
                    vec_set_func = self.runtime.get_function("vec_str_set")
                    return self.builder.call(vec_set_func, [container_ptr, key_or_index, value], name="")
                else:
                    # Integer list (default): vec_int_set(list_ptr, index, i64 value)
                    vec_set_func = self.runtime.get_function("vec_int_set")
                    return self.builder.call(vec_set_func, [container_ptr, key_or_index, value], name="")
            else:
                # Fallback to vec_int
                vec_set_func = self.runtime.get_function("vec_int_set")
                return self.builder.call(vec_set_func, [container_ptr, key_or_index, value], name="")

        elif node.function_name == "__contains__":
            # key in dict -> map_str_int_contains(dict_ptr, key)
            if len(node.arguments) != 2:
                raise RuntimeError("__contains__() requires exactly 2 arguments (container and key)")

            container_ptr = node.arguments[0].accept(self)  # Container pointer
            key = node.arguments[1].accept(self)  # Key to check

            # Determine container type
            from llvmlite import ir as llvm_ir

            container_type = container_ptr.type

            if isinstance(container_type, llvm_ir.PointerType):
                pointee_type_str = str(container_type.pointee)

                if "map_str_int" in pointee_type_str:
                    # Dict: map_str_int_contains(dict_ptr, key) returns i32
                    map_contains_func = self.runtime.get_function("map_str_int_contains")
                    result = self.builder.call(map_contains_func, [container_ptr, key], name="contains_result")
                    # Convert i32 result to i1 (bool) by comparing with 0
                    return self.builder.icmp_signed("!=", result, ir.Constant(ir.IntType(32), 0), name="contains_bool")
                else:
                    # TODO: Add list contains support if needed
                    raise NotImplementedError(f"__contains__ not implemented for type {pointee_type_str}")
            else:
                raise NotImplementedError("__contains__ requires a pointer type")

        elif node.function_name == "__method_split__":
            # str.split(delimiter) -> multigen_str_split(str, delimiter)
            # Returns multigen_string_array_t* which we bitcast to vec_str*
            if len(node.arguments) < 1 or len(node.arguments) > 2:
                raise RuntimeError("split() requires 1 or 2 arguments (string and optional delimiter)")

            str_ptr = node.arguments[0].accept(self)  # char* string

            # Get delimiter (default to empty string for whitespace splitting)
            if len(node.arguments) == 2:
                delim_ptr = node.arguments[1].accept(self)
            else:
                # Empty string means split on whitespace
                empty_str = self._create_string_constant("")
                delim_ptr = empty_str

            # Call multigen_str_split (returns multigen_string_array_t*)
            split_func = self.runtime.get_function("multigen_str_split")
            string_array_result = self.builder.call(split_func, [str_ptr, delim_ptr], name="split_result")

            # Bitcast multigen_string_array_t* to vec_str* (same layout: char**, size_t, size_t)
            vec_str_ptr_type = self.runtime.get_vec_str_type().as_pointer()
            vec_str_result = self.builder.bitcast(string_array_result, vec_str_ptr_type, name="split_as_vec_str")
            return vec_str_result

        elif node.function_name == "__method_lower__":
            # str.lower() -> multigen_str_lower(str)
            if len(node.arguments) != 1:
                raise RuntimeError("lower() requires exactly 1 argument (string)")

            str_ptr = node.arguments[0].accept(self)
            lower_func = self.runtime.get_function("multigen_str_lower")
            return self.builder.call(lower_func, [str_ptr], name="lower_result")

        elif node.function_name == "__method_strip__":
            # str.strip() -> multigen_str_strip(str)
            if len(node.arguments) != 1:
                raise RuntimeError("strip() requires exactly 1 argument (string)")

            str_ptr = node.arguments[0].accept(self)
            strip_func = self.runtime.get_function("multigen_str_strip")
            return self.builder.call(strip_func, [str_ptr], name="strip_result")

        elif node.function_name == "__method_upper__":
            # str.upper() -> multigen_str_upper(str)
            if len(node.arguments) != 1:
                raise RuntimeError("upper() requires exactly 1 argument (string)")

            str_ptr = node.arguments[0].accept(self)
            upper_func = self.runtime.get_function("multigen_str_upper")
            return self.builder.call(upper_func, [str_ptr], name="upper_result")

        elif node.function_name == "__method_replace__":
            # str.replace(old, new) -> multigen_str_replace(str, old, new)
            if len(node.arguments) != 3:
                raise RuntimeError("replace() requires exactly 3 arguments (string, old, new)")

            str_ptr = node.arguments[0].accept(self)
            old_ptr = node.arguments[1].accept(self)
            new_ptr = node.arguments[2].accept(self)
            replace_func = self.runtime.get_function("multigen_str_replace")
            return self.builder.call(replace_func, [str_ptr, old_ptr, new_ptr], name="replace_result")

        elif node.function_name == "__method_startswith__":
            # str.startswith(prefix) -> multigen_str_startswith(str, prefix) returns i32
            if len(node.arguments) != 2:
                raise RuntimeError("startswith() requires exactly 2 arguments (string, prefix)")

            str_ptr = node.arguments[0].accept(self)
            prefix_ptr = node.arguments[1].accept(self)
            startswith_func = self.runtime.get_function("multigen_str_startswith")
            result = self.builder.call(startswith_func, [str_ptr, prefix_ptr], name="startswith_result")
            # Convert i32 result to i1 (bool) by comparing with 0
            return self.builder.icmp_signed("!=", result, ir.Constant(ir.IntType(32), 0), name="startswith_bool")

        elif node.function_name == "__method_endswith__":
            # str.endswith(suffix) -> multigen_str_endswith(str, suffix) returns i32
            if len(node.arguments) != 2:
                raise RuntimeError("endswith() requires exactly 2 arguments (string, suffix)")

            str_ptr = node.arguments[0].accept(self)
            suffix_ptr = node.arguments[1].accept(self)
            endswith_func = self.runtime.get_function("multigen_str_endswith")
            result = self.builder.call(endswith_func, [str_ptr, suffix_ptr], name="endswith_result")
            # Convert i32 result to i1 (bool) by comparing with 0
            return self.builder.icmp_signed("!=", result, ir.Constant(ir.IntType(32), 0), name="endswith_bool")

        elif node.function_name == "__method_join__":
            # separator.join(list) -> multigen_str_join(separator, list)
            # Note: list should be vec_str* which has same layout as multigen_string_array_t*
            if len(node.arguments) != 2:
                raise RuntimeError("join() requires exactly 2 arguments (separator, list)")

            sep_ptr = node.arguments[0].accept(self)
            list_ptr = node.arguments[1].accept(self)

            # Bitcast vec_str* to multigen_string_array_t* (same layout: char**, size_t, size_t)
            string_array_ptr_type = self.runtime.get_string_array_type().as_pointer()
            string_array_ptr = self.builder.bitcast(list_ptr, string_array_ptr_type, name="list_as_string_array")

            join_func = self.runtime.get_function("multigen_str_join")
            return self.builder.call(join_func, [sep_ptr, string_array_ptr], name="join_result")

        elif node.function_name == "__method_values__":
            # dict.values() -> create and return vec_int with all values
            if len(node.arguments) != 1:
                raise RuntimeError("values() requires exactly 1 argument (dict)")

            dict_ptr = node.arguments[0].accept(self)
            return self._generate_dict_values_vec(dict_ptr)

        elif node.function_name == "__method_items__":
            # dict.items() - not directly callable, should only be used in for loops
            raise NotImplementedError("dict.items() can only be used in for loops or comprehensions")

        # Handle builtin functions
        elif node.function_name == "len":
            # len() function - use strlen for strings, vec_*_size for lists, map_*_size for dicts
            if len(node.arguments) != 1:
                raise NotImplementedError("len() requires exactly one argument")

            arg = node.arguments[0]
            llvm_arg = arg.accept(self)

            if arg.result_type.base_type == IRDataType.STRING:
                # Use C strlen function
                i8_ptr = ir.IntType(8).as_pointer()
                i64 = ir.IntType(64)
                strlen_func = self._get_or_create_c_function("strlen", i64, [i8_ptr])
                return self.builder.call(strlen_func, [llvm_arg], name="len_tmp")
            elif arg.result_type.base_type == IRDataType.DICT:
                # Use map_*_size function from runtime
                # llvm_arg is already a pointer
                from llvmlite import ir as llvm_ir

                if isinstance(llvm_arg.type, llvm_ir.PointerType):
                    pointee_type_str = str(llvm_arg.type.pointee)

                    if "map_str_int" in pointee_type_str:
                        map_size_func = self.runtime.get_function("map_str_int_size")
                        return self.builder.call(map_size_func, [llvm_arg], name="len_tmp")
                    elif "map_int_int" in pointee_type_str:
                        map_size_func = self.runtime.get_function("map_int_int_size")
                        return self.builder.call(map_size_func, [llvm_arg], name="len_tmp")
                    else:
                        raise NotImplementedError(f"len() for dict type {pointee_type_str} not implemented")
                else:
                    raise NotImplementedError("len() requires a pointer type for dicts")
            elif arg.result_type.base_type == IRDataType.LIST:
                # Use vec_*_size function from runtime based on element type
                # llvm_arg is already a pointer
                from llvmlite import ir as llvm_ir

                # Check the pointee type to determine which vec_* type we have
                if isinstance(llvm_arg.type, llvm_ir.PointerType):
                    pointee_type_str = str(llvm_arg.type.pointee)

                    if "vec_vec_int" in pointee_type_str:
                        vec_size_func = self.runtime.get_function("vec_vec_int_size")
                    elif "vec_str" in pointee_type_str:
                        vec_size_func = self.runtime.get_function("vec_str_size")
                    else:
                        vec_size_func = self.runtime.get_function("vec_int_size")
                else:
                    # Fallback to vec_int
                    vec_size_func = self.runtime.get_function("vec_int_size")

                return self.builder.call(vec_size_func, [llvm_arg], name="len_tmp")
            elif arg.result_type.base_type == IRDataType.SET:
                # Use set_int_size function from runtime
                # llvm_arg is already a pointer
                set_size_func = self.runtime.get_function("set_int_size")
                return self.builder.call(set_size_func, [llvm_arg], name="len_tmp")
            else:
                raise NotImplementedError(f"len() for type {arg.result_type.base_type} not implemented")

        elif node.function_name == "print":
            # Get or create printf declaration
            arg_types = [arg.result_type for arg in node.arguments]
            printf_func = self._get_or_create_builtin("print", arg_types)

            # Create format string based on argument type
            if len(node.arguments) == 1:
                arg = node.arguments[0]
                if arg.result_type.base_type == IRDataType.INT:
                    fmt_bytes = b"%lld\n\x00"  # %lld\n\0 as bytes
                elif arg.result_type.base_type == IRDataType.FLOAT:
                    fmt_bytes = b"%f\n\x00"  # %f\n\0 as bytes
                elif arg.result_type.base_type == IRDataType.BOOL:
                    fmt_bytes = b"%d\n\x00"  # %d\n\0 as bytes
                elif arg.result_type.base_type == IRDataType.STRING:
                    fmt_bytes = b"%s\n\x00"  # %s\n\0 as bytes
                else:
                    raise NotImplementedError(f"Print for type {arg.result_type.base_type} not implemented")

                # Create global string constant for format
                fmt_const = ir.Constant(ir.ArrayType(ir.IntType(8), len(fmt_bytes)), bytearray(fmt_bytes))
                fmt_global = ir.GlobalVariable(self.module, fmt_const.type, name=f"fmt_{len(self.module.globals)}")
                fmt_global.linkage = "internal"
                fmt_global.global_constant = True
                fmt_global.initializer = fmt_const

                # Get pointer to the format string
                fmt_ptr = self.builder.gep(fmt_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])

                # Evaluate argument and call printf
                llvm_arg = arg.accept(self)
                return self.builder.call(printf_func, [fmt_ptr, llvm_arg], name="print_tmp")
            else:
                raise NotImplementedError("Print with multiple arguments not implemented")

        # Regular function call
        func = self.func_symtab.get(node.function_name)
        if func is None:
            raise RuntimeError(f"Function '{node.function_name}' not found in symbol table")

        args = [arg.accept(self) for arg in node.arguments]
        return self.builder.call(func, args, name="call_tmp")

    def visit_type_cast(self, node: IRTypeCast) -> ir.Instruction:
        """Convert IR type cast to LLVM cast instruction.

        Args:
            node: IR type cast to convert

        Returns:
            LLVM cast instruction
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        value = node.value.accept(self)
        source_type = node.value.result_type.base_type
        target_type = node.result_type.base_type

        # INT to FLOAT
        if source_type == IRDataType.INT and target_type == IRDataType.FLOAT:
            llvm_target = self._convert_type(node.result_type)
            return self.builder.sitofp(value, llvm_target, name="cast_tmp")

        # FLOAT to INT
        elif source_type == IRDataType.FLOAT and target_type == IRDataType.INT:
            llvm_target = self._convert_type(node.result_type)
            return self.builder.fptosi(value, llvm_target, name="cast_tmp")

        # INT to BOOL
        elif source_type == IRDataType.INT and target_type == IRDataType.BOOL:
            zero = ir.Constant(ir.IntType(64), 0)
            return self.builder.icmp_signed("!=", value, zero, name="cast_tmp")

        # FLOAT to BOOL
        elif source_type == IRDataType.FLOAT and target_type == IRDataType.BOOL:
            zero = ir.Constant(ir.DoubleType(), 0.0)
            return self.builder.fcmp_ordered("!=", value, zero, name="cast_tmp")

        # BOOL to INT
        elif source_type == IRDataType.BOOL and target_type == IRDataType.INT:
            llvm_target = self._convert_type(node.result_type)
            return self.builder.zext(value, llvm_target, name="cast_tmp")

        # Same type - no cast needed
        elif source_type == target_type:
            return value

        # Unsupported cast
        else:
            raise NotImplementedError(f"Type cast from {source_type} to {target_type} not implemented")

    def visit_return(self, node: IRReturn) -> None:
        """Convert IR return statement to LLVM ret instruction.

        Args:
            node: IR return statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        if node.value:
            ret_val = node.value.accept(self)
            self.builder.ret(ret_val)
        else:
            self.builder.ret_void()

    def visit_break(self, node: IRBreak) -> None:
        """Convert IR break statement to LLVM branch to loop exit.

        Args:
            node: IR break statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        if not self.loop_exit_stack:
            raise RuntimeError("Break statement outside of loop")

        # Branch to the current loop's exit block
        self.builder.branch(self.loop_exit_stack[-1])

    def visit_continue(self, node: IRContinue) -> None:
        """Convert IR continue statement to LLVM branch to loop condition.

        Args:
            node: IR continue statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        if not self.loop_continue_stack:
            raise RuntimeError("Continue statement outside of loop")

        # Branch to the current loop's condition block
        self.builder.branch(self.loop_continue_stack[-1])

    def visit_expression_statement(self, node: IRExpressionStatement) -> None:
        """Convert IR expression statement to LLVM.

        Args:
            node: IR expression statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Evaluate the expression (e.g., void function call)
        # The expression's side effects (like function calls) will be executed
        node.expression.accept(self)

    def visit_if(self, node: IRIf) -> None:
        """Convert IR if statement to LLVM basic blocks with branches.

        Args:
            node: IR if statement to convert
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Evaluate condition
        cond = node.condition.accept(self)

        # Create basic blocks
        then_block = self.current_function.append_basic_block("if.then")
        else_block = self.current_function.append_basic_block("if.else")
        merge_block = self.current_function.append_basic_block("if.merge")

        # Branch on condition
        self.builder.cbranch(cond, then_block, else_block)

        # Generate then block
        self.builder.position_at_end(then_block)
        for stmt in node.then_body:
            stmt.accept(self)
        if not self.builder.block.is_terminated:
            self.builder.branch(merge_block)

        # Generate else block
        self.builder.position_at_end(else_block)
        for stmt in node.else_body:
            stmt.accept(self)
        if not self.builder.block.is_terminated:
            self.builder.branch(merge_block)

        # Continue at merge point
        self.builder.position_at_end(merge_block)

    def visit_while(self, node: IRWhile) -> None:
        """Convert IR while loop to LLVM loop blocks.

        Args:
            node: IR while loop to convert
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Create basic blocks
        cond_block = self.current_function.append_basic_block("while.cond")
        body_block = self.current_function.append_basic_block("while.body")
        exit_block = self.current_function.append_basic_block("while.exit")

        # Track loop blocks for break/continue
        self.loop_exit_stack.append(exit_block)
        self.loop_continue_stack.append(cond_block)

        # Jump to condition check
        self.builder.branch(cond_block)

        # Generate condition block
        self.builder.position_at_end(cond_block)
        cond = node.condition.accept(self)
        self.builder.cbranch(cond, body_block, exit_block)

        # Generate body block
        self.builder.position_at_end(body_block)
        for stmt in node.body:
            stmt.accept(self)
        if not self.builder.block.is_terminated:
            self.builder.branch(cond_block)  # Loop back

        # Pop loop blocks from stack
        self.loop_exit_stack.pop()
        self.loop_continue_stack.pop()

        # Continue after loop
        self.builder.position_at_end(exit_block)

    def visit_for(self, node: IRFor) -> None:
        """Convert IR for loop (range-based) to LLVM loop blocks.

        Args:
            node: IR for loop to convert
        """
        if self.builder is None or self.current_function is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Allocate loop variable
        loop_var_type = self._convert_type(node.variable.ir_type)
        loop_var_ptr = self.builder.alloca(loop_var_type, name=node.variable.name)
        self.var_symtab[node.variable.name] = loop_var_ptr

        # Initialize loop variable with start value
        start_val = node.start.accept(self)
        self.builder.store(start_val, loop_var_ptr)

        # Create basic blocks
        cond_block = self.current_function.append_basic_block("for.cond")
        body_block = self.current_function.append_basic_block("for.body")
        inc_block = self.current_function.append_basic_block("for.inc")
        exit_block = self.current_function.append_basic_block("for.exit")

        # Track loop blocks for break/continue
        self.loop_exit_stack.append(exit_block)
        self.loop_continue_stack.append(inc_block)  # continue jumps to increment

        # Jump to condition
        self.builder.branch(cond_block)

        # Condition: loop_var < end (or > end for negative step)
        self.builder.position_at_end(cond_block)
        loop_var_val = self.builder.load(loop_var_ptr)
        end_val = node.end.accept(self)

        # Determine comparison operator based on step value
        # For negative steps, use >, for positive steps use <
        from ...frontend.static_ir import IRBinaryOperation, IRLiteral

        def is_negative_step(step: Optional[IRExpression]) -> bool:
            """Check if step is a negative constant."""
            if step is None:
                return False
            if isinstance(step, IRLiteral):
                return isinstance(step.value, int) and step.value < 0
            # Handle negative literals encoded as 0 - N
            if isinstance(step, IRBinaryOperation) and step.operator == "-":
                if isinstance(step.left, IRLiteral) and step.left.value == 0:
                    if isinstance(step.right, IRLiteral) and isinstance(step.right.value, int):
                        return step.right.value > 0
            return False

        comparison_op = ">" if is_negative_step(node.step) else "<"
        cond = self.builder.icmp_signed(comparison_op, loop_var_val, end_val, name="for.cond")
        self.builder.cbranch(cond, body_block, exit_block)

        # Body
        self.builder.position_at_end(body_block)
        for stmt in node.body:
            stmt.accept(self)
        if not self.builder.block.is_terminated:
            self.builder.branch(inc_block)

        # Increment
        self.builder.position_at_end(inc_block)
        loop_var_val = self.builder.load(loop_var_ptr)
        if node.step:
            step_val = node.step.accept(self)
        else:
            step_val = ir.Constant(loop_var_type, 1)
        next_val = self.builder.add(loop_var_val, step_val, name="for.inc")
        self.builder.store(next_val, loop_var_ptr)
        self.builder.branch(cond_block)

        # Pop loop blocks from stack
        self.loop_exit_stack.pop()
        self.loop_continue_stack.pop()

        # Exit
        self.builder.position_at_end(exit_block)

    def visit_type_declaration(self, node: IRTypeDeclaration) -> None:
        """Visit a type declaration node (structs, unions, enums).

        Args:
            node: IR type declaration to convert
        """
        # Type declarations will be implemented when needed for complex types
        # For now, we focus on basic types
        pass

    def visit_try(self, node: IRTry) -> None:
        """Visit a try/except statement node.

        Note: LLVM exception handling is complex and requires proper personality
        functions and landing pads. For now, we just execute the try body
        without exception handling support.

        Args:
            node: IR try statement to convert
        """
        # LLVM exception handling requires EH personality functions and
        # landing pads which are complex to implement. For now, just
        # execute the try body directly.
        for stmt in node.body:
            stmt.accept(self)
        # TODO: Implement proper LLVM exception handling with invoke/landingpad

    def visit_raise(self, node: IRRaise) -> None:
        """Visit a raise statement node.

        Note: LLVM exception raising requires runtime support for unwinding.
        For now, we simply terminate the program.

        Args:
            node: IR raise statement to convert
        """
        # LLVM exception raising requires runtime unwinding support.
        # For now, call abort() to terminate the program.
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Call the abort() function from C runtime
        abort_func = self.module.globals.get("abort")
        if abort_func is None:
            abort_type = ir.FunctionType(ir.VoidType(), [])
            abort_func = ir.Function(self.module, abort_type, name="abort")

        self.builder.call(abort_func, [])
        self.builder.unreachable()
        # TODO: Implement proper LLVM exception throwing with resume instruction

    def visit_with(self, node: IRWith) -> None:
        """Visit a with statement node (context manager).

        Note: LLVM context managers require file I/O runtime support.
        This is a stub implementation that visits the body statements.
        Full file I/O support is planned for future implementation.

        Args:
            node: IR with statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # TODO: Implement proper file I/O with fopen/fclose runtime calls
        # For now, just visit the body statements without actual file handling
        # This allows the rest of the code to compile but file operations won't work
        for stmt in node.body:
            stmt.accept(self)

    def visit_yield(self, node: IRYield) -> None:
        """Visit a yield statement node (generator).

        Note: LLVM generator support requires vec runtime support for accumulation.
        This is a stub implementation that generates a no-op.
        Full generator support is planned for future implementation.

        Args:
            node: IR yield statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # TODO: Implement proper generator support with vec_int_push runtime calls
        # For now, evaluate the value expression (for side effects) but discard result
        node.value.accept(self)

    def visit_yield_from(self, node: IRYieldFrom) -> None:
        """Visit a yield from statement node (generator).

        Stub implementation consistent with visit_yield.
        Evaluates the iterable expression for side effects only.

        Args:
            node: IR yield from statement to convert
        """
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # TODO: Implement proper generator support with vec extension runtime calls
        # For now, evaluate the iterable expression (for side effects) but discard result
        node.iterable.accept(self)

    def _convert_type(self, ir_type: IRType) -> ir.Type:
        """Map IRType to llvmlite type.

        Args:
            ir_type: MultiGen IR type

        Returns:
            Corresponding llvmlite type
        """
        # Handle list types (LIST<T>)
        if ir_type.base_type == IRDataType.LIST:
            # Lists are represented as pointers to vec_* structs
            # Check element type to determine which vec_* type to use
            if hasattr(ir_type, "element_type") and ir_type.element_type:
                elem_type = ir_type.element_type.base_type

                if elem_type == IRDataType.LIST:
                    # 2D list: list[list[int]] -> vec_vec_int*
                    return self.runtime.get_vec_vec_int_type().as_pointer()
                elif elem_type == IRDataType.STRING:
                    # String list: list[str] -> vec_str*
                    return self.runtime.get_vec_str_type().as_pointer()
                elif elem_type == IRDataType.INT:
                    # Integer list: list[int] -> vec_int*
                    return self.runtime.get_vec_int_type().as_pointer()
                else:
                    # Default to vec_int for other types
                    return self.runtime.get_vec_int_type().as_pointer()
            else:
                # No element type info, default to vec_int
                return self.runtime.get_vec_int_type().as_pointer()

        # Handle dict types (DICT<K, V>)
        if ir_type.base_type == IRDataType.DICT:
            # Dicts are represented as pointers to map_* structs
            # Check element_type to determine key type
            if ir_type.element_type and ir_type.element_type.base_type == IRDataType.STRING:
                # String keys -> map_str_int*
                return self.runtime.get_map_str_int_type().as_pointer()
            else:
                # Default to int keys -> map_int_int*
                # This handles both dict[int, int] and generic dict
                return self.runtime.get_map_int_int_type().as_pointer()

        # Handle set types (SET<T>)
        if ir_type.base_type == IRDataType.SET:
            # Sets are represented as pointers to set_* structs
            # For now, only support int sets -> set_int*
            return self.runtime.get_set_int_type().as_pointer()

        # Base type mapping
        type_mapping = {
            IRDataType.VOID: ir.VoidType(),
            IRDataType.INT: ir.IntType(64),  # 64-bit integer
            IRDataType.FLOAT: ir.DoubleType(),  # double precision
            IRDataType.BOOL: ir.IntType(1),  # i1
            IRDataType.STRING: ir.IntType(8).as_pointer(),  # char*
        }

        base = type_mapping.get(ir_type.base_type, ir.VoidType())

        # Handle pointers
        if ir_type.is_pointer or ir_type.pointer_depth > 0:
            depth = ir_type.pointer_depth or 1
            for _ in range(depth):
                base = base.as_pointer()

        # Handle arrays
        if ir_type.array_dimensions:
            # Build array types from innermost to outermost
            for dim in reversed(ir_type.array_dimensions):
                if dim:
                    base = ir.ArrayType(base, dim)
                else:
                    # Unknown dimension, use pointer
                    base = base.as_pointer()

        return base

__init__()

Initialize the LLVM IR converter.

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def __init__(self) -> None:
    """Initialize the LLVM IR converter."""
    self.module: ir.Module = ir.Module(name="multigen_module")
    # Set target triple to empty string to use native target
    # llvmlite will use the host's target triple
    self.module.triple = ""
    self.builder: Optional[ir.IRBuilder] = None
    self.func_symtab: dict[str, ir.Function] = {}
    self.var_symtab: dict[str, ir.AllocaInstr] = {}
    self.global_symtab: dict[str, ir.GlobalVariable] = {}
    self.current_function: Optional[ir.Function] = None
    # Track current loop blocks for break/continue
    self.loop_exit_stack: list[ir.Block] = []
    self.loop_continue_stack: list[ir.Block] = []
    # Runtime declarations for C library
    self.runtime = LLVMRuntimeDeclarations(self.module)

visit_assignment(node)

Convert IR assignment to LLVM store instruction.

Parameters:

Name Type Description Default
node IRAssignment

IR assignment to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_assignment(self, node: IRAssignment) -> None:
    """Convert IR assignment to LLVM store instruction.

    Args:
        node: IR assignment to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Generate value expression first (we might need its type)
    value = None
    if node.value:
        value = node.value.accept(self)

    # Check if it's a global variable first
    if node.target.name in self.global_symtab:
        var_ptr = self.global_symtab[node.target.name]
    elif node.target.name not in self.var_symtab:
        # Allocate new local variable using IR type
        # For lists, we store pointers in variables, so we alloca space for a pointer
        var_type = self._convert_type(node.target.ir_type)
        var_ptr = self.builder.alloca(var_type, name=node.target.name)
        self.var_symtab[node.target.name] = var_ptr
    else:
        var_ptr = self.var_symtab[node.target.name]

    # Store the value if present
    if value is not None:
        # Check for list dimensionality mismatch (1D vs 2D list literals only)
        value_type_str = str(value.type)
        var_type_str = str(var_ptr.type)

        # Detect actual dimension mismatch:
        # - value is vec_int* (1D) but variable expects vec_vec_int* (2D)
        # - value is vec_vec_int* (2D) but variable expects vec_int* (1D)
        value_is_1d = "vec_int" in value_type_str and "vec_vec_int" not in value_type_str
        value_is_2d = "vec_vec_int" in value_type_str
        var_is_1d = "vec_int" in var_type_str and "vec_vec_int" not in var_type_str
        var_is_2d = "vec_vec_int" in var_type_str

        is_dimension_mismatch = (value_is_1d and var_is_2d) or (value_is_2d and var_is_1d)

        if is_dimension_mismatch:
            # Don't store the mismatched value - it's an empty list with wrong dimension
            # The variable will remain uninitialized, which is fine - it will be initialized
            # on first use (e.g., append)
            pass
        else:
            # Types match - normal store
            self.builder.store(value, var_ptr)
    else:
        # No value provided - initialize pointer types to NULL
        # This handles cases like `result: dict = {}` where we defer the literal
        var_type_str = str(var_ptr.type)
        if "map_" in var_type_str or "vec_" in var_type_str or "set_" in var_type_str:
            # Initialize pointer to NULL (0)
            pointee_type = var_ptr.type.pointee
            null_value = ir.Constant(pointee_type, None)
            self.builder.store(null_value, var_ptr)

visit_binary_operation(node)

Convert IR binary operation to LLVM instruction.

Parameters:

Name Type Description Default
node IRBinaryOperation

IR binary operation to convert

required

Returns:

Type Description
Instruction

LLVM instruction representing the operation

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_binary_operation(self, node: IRBinaryOperation) -> ir.Instruction:
    """Convert IR binary operation to LLVM instruction.

    Args:
        node: IR binary operation to convert

    Returns:
        LLVM instruction representing the operation
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # For short-circuit operators (and, or), we need special handling
    # to avoid evaluating the right side when not necessary
    if node.result_type.base_type == IRDataType.BOOL and node.operator in ("and", "or"):
        return self._visit_short_circuit_boolean(node)

    # For all other operators, evaluate both sides immediately
    left = node.left.accept(self)
    right = node.right.accept(self)

    # String operations
    if node.left.result_type.base_type == IRDataType.STRING:
        if node.operator == "+":
            # String concatenation using C library functions
            return self._concat_strings(left, right)

    # Integer operations
    if node.result_type.base_type == IRDataType.INT:
        if node.operator == "+":
            return self.builder.add(left, right, name="add_tmp")
        elif node.operator == "-":
            return self.builder.sub(left, right, name="sub_tmp")
        elif node.operator == "*":
            return self.builder.mul(left, right, name="mul_tmp")
        elif node.operator == "/" or node.operator == "//":
            return self.builder.sdiv(left, right, name="div_tmp")
        elif node.operator == "%":
            # Python modulo uses floored division, C uses truncated division
            # To convert: if remainder and divisor have different signs, add divisor to remainder
            c_rem = self.builder.srem(left, right, name="c_rem")

            # Check if signs differ: (c_rem < 0) != (right < 0)
            zero = ir.Constant(ir.IntType(64), 0)
            rem_neg = self.builder.icmp_signed("<", c_rem, zero, name="rem_neg")
            divisor_neg = self.builder.icmp_signed("<", right, zero, name="divisor_neg")
            signs_differ = self.builder.xor(rem_neg, divisor_neg, name="signs_differ")

            # Check if remainder is non-zero
            rem_nonzero = self.builder.icmp_signed("!=", c_rem, zero, name="rem_nonzero")

            # Adjust if signs differ AND remainder is non-zero
            need_adjust = self.builder.and_(signs_differ, rem_nonzero, name="need_adjust")

            # result = need_adjust ? (c_rem + right) : c_rem
            adjusted = self.builder.add(c_rem, right, name="adjusted")
            result = self.builder.select(need_adjust, adjusted, c_rem, name="mod_tmp")
            return result
        elif node.operator == "<<":
            return self.builder.shl(left, right, name="shl_tmp")
        elif node.operator == ">>":
            return self.builder.ashr(left, right, name="shr_tmp")
        elif node.operator == "&":
            return self.builder.and_(left, right, name="and_tmp")
        elif node.operator == "|":
            return self.builder.or_(left, right, name="or_tmp")
        elif node.operator == "^":
            return self.builder.xor(left, right, name="xor_tmp")

    # Float operations
    elif node.result_type.base_type == IRDataType.FLOAT:
        if node.operator == "+":
            return self.builder.fadd(left, right, name="fadd_tmp")
        elif node.operator == "-":
            return self.builder.fsub(left, right, name="fsub_tmp")
        elif node.operator == "*":
            return self.builder.fmul(left, right, name="fmul_tmp")
        elif node.operator == "/":
            return self.builder.fdiv(left, right, name="fdiv_tmp")

    # Boolean operations (comparisons)
    elif node.result_type.base_type == IRDataType.BOOL:
        # Determine operand types to choose correct comparison instruction
        left_type = node.left.result_type.base_type

        if left_type == IRDataType.INT:
            # Integer comparisons (icmp)
            if node.operator == "<":
                return self.builder.icmp_signed("<", left, right, name="cmp_tmp")
            elif node.operator == "<=":
                return self.builder.icmp_signed("<=", left, right, name="cmp_tmp")
            elif node.operator == ">":
                return self.builder.icmp_signed(">", left, right, name="cmp_tmp")
            elif node.operator == ">=":
                return self.builder.icmp_signed(">=", left, right, name="cmp_tmp")
            elif node.operator == "==":
                return self.builder.icmp_signed("==", left, right, name="cmp_tmp")
            elif node.operator == "!=":
                return self.builder.icmp_signed("!=", left, right, name="cmp_tmp")
        elif left_type == IRDataType.FLOAT:
            # Float comparisons (fcmp)
            if node.operator == "<":
                return self.builder.fcmp_ordered("<", left, right, name="fcmp_tmp")
            elif node.operator == "<=":
                return self.builder.fcmp_ordered("<=", left, right, name="fcmp_tmp")
            elif node.operator == ">":
                return self.builder.fcmp_ordered(">", left, right, name="fcmp_tmp")
            elif node.operator == ">=":
                return self.builder.fcmp_ordered(">=", left, right, name="fcmp_tmp")
            elif node.operator == "==":
                return self.builder.fcmp_ordered("==", left, right, name="fcmp_tmp")
            elif node.operator == "!=":
                return self.builder.fcmp_ordered("!=", left, right, name="fcmp_tmp")
        elif left_type == IRDataType.BOOL:
            # Boolean comparisons (and/or handled separately via _visit_short_circuit_boolean)
            if node.operator == "==":
                return self.builder.icmp_signed("==", left, right, name="cmp_tmp")
            elif node.operator == "!=":
                return self.builder.icmp_signed("!=", left, right, name="cmp_tmp")

        # Handle "in" operator for dict membership testing
        # Example: "key" in dict -> map_str_int_contains(dict, key)
        if node.operator == "in":
            # right operand should be the container (dict)
            right_type = node.right.result_type.base_type
            if right_type == IRDataType.DICT:
                # Determine dict type to select appropriate contains function
                if (
                    node.right.result_type.element_type
                    and node.right.result_type.element_type.base_type == IRDataType.STRING
                ):
                    map_contains_func = self.runtime.get_function("map_str_int_contains")
                else:
                    map_contains_func = self.runtime.get_function("map_int_int_contains")
                result = self.builder.call(map_contains_func, [right, left], name="contains_result")
                # Convert i32 result to i1 (bool)
                zero = ir.Constant(ir.IntType(32), 0)
                return self.builder.icmp_signed("!=", result, zero, name="contains_bool")

    raise NotImplementedError(
        f"Binary operator '{node.operator}' not implemented for type {node.result_type.base_type}"
    )

visit_break(node)

Convert IR break statement to LLVM branch to loop exit.

Parameters:

Name Type Description Default
node IRBreak

IR break statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_break(self, node: IRBreak) -> None:
    """Convert IR break statement to LLVM branch to loop exit.

    Args:
        node: IR break statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    if not self.loop_exit_stack:
        raise RuntimeError("Break statement outside of loop")

    # Branch to the current loop's exit block
    self.builder.branch(self.loop_exit_stack[-1])

visit_comprehension(node)

Convert IR comprehension to LLVM loop with append/insert operations.

Handles comprehensions like: - List: [expr for var in iterable if condition] - Dict: {key: value for var in iterable if condition} - Set: {expr for var in iterable if condition}

Generates: 1. Allocate result container 2. Generate for loop 3. Add conditional if present 4. Append/insert expression to result

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_comprehension(self, node: IRComprehension) -> ir.Value:
    """Convert IR comprehension to LLVM loop with append/insert operations.

    Handles comprehensions like:
    - List: [expr for var in iterable if condition]
    - Dict: {key: value for var in iterable if condition}
    - Set: {expr for var in iterable if condition}

    Generates:
    1. Allocate result container
    2. Generate for loop
    3. Add conditional if present
    4. Append/insert expression to result
    """
    if self.builder is None or self.current_function is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    ast_node = node.ast_node

    # Determine comprehension type
    if isinstance(ast_node, ast.ListComp):
        return self._visit_list_comprehension(node, ast_node)
    elif isinstance(ast_node, ast.DictComp):
        return self._visit_dict_comprehension(node, ast_node)
    elif isinstance(ast_node, ast.SetComp):
        return self._visit_set_comprehension(node, ast_node)
    else:
        raise NotImplementedError(f"Unsupported comprehension type: {type(ast_node).__name__}")

visit_continue(node)

Convert IR continue statement to LLVM branch to loop condition.

Parameters:

Name Type Description Default
node IRContinue

IR continue statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_continue(self, node: IRContinue) -> None:
    """Convert IR continue statement to LLVM branch to loop condition.

    Args:
        node: IR continue statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    if not self.loop_continue_stack:
        raise RuntimeError("Continue statement outside of loop")

    # Branch to the current loop's condition block
    self.builder.branch(self.loop_continue_stack[-1])

visit_expression_statement(node)

Convert IR expression statement to LLVM.

Parameters:

Name Type Description Default
node IRExpressionStatement

IR expression statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_expression_statement(self, node: IRExpressionStatement) -> None:
    """Convert IR expression statement to LLVM.

    Args:
        node: IR expression statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Evaluate the expression (e.g., void function call)
    # The expression's side effects (like function calls) will be executed
    node.expression.accept(self)

visit_for(node)

Convert IR for loop (range-based) to LLVM loop blocks.

Parameters:

Name Type Description Default
node IRFor

IR for loop to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_for(self, node: IRFor) -> None:
    """Convert IR for loop (range-based) to LLVM loop blocks.

    Args:
        node: IR for loop to convert
    """
    if self.builder is None or self.current_function is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Allocate loop variable
    loop_var_type = self._convert_type(node.variable.ir_type)
    loop_var_ptr = self.builder.alloca(loop_var_type, name=node.variable.name)
    self.var_symtab[node.variable.name] = loop_var_ptr

    # Initialize loop variable with start value
    start_val = node.start.accept(self)
    self.builder.store(start_val, loop_var_ptr)

    # Create basic blocks
    cond_block = self.current_function.append_basic_block("for.cond")
    body_block = self.current_function.append_basic_block("for.body")
    inc_block = self.current_function.append_basic_block("for.inc")
    exit_block = self.current_function.append_basic_block("for.exit")

    # Track loop blocks for break/continue
    self.loop_exit_stack.append(exit_block)
    self.loop_continue_stack.append(inc_block)  # continue jumps to increment

    # Jump to condition
    self.builder.branch(cond_block)

    # Condition: loop_var < end (or > end for negative step)
    self.builder.position_at_end(cond_block)
    loop_var_val = self.builder.load(loop_var_ptr)
    end_val = node.end.accept(self)

    # Determine comparison operator based on step value
    # For negative steps, use >, for positive steps use <
    from ...frontend.static_ir import IRBinaryOperation, IRLiteral

    def is_negative_step(step: Optional[IRExpression]) -> bool:
        """Check if step is a negative constant."""
        if step is None:
            return False
        if isinstance(step, IRLiteral):
            return isinstance(step.value, int) and step.value < 0
        # Handle negative literals encoded as 0 - N
        if isinstance(step, IRBinaryOperation) and step.operator == "-":
            if isinstance(step.left, IRLiteral) and step.left.value == 0:
                if isinstance(step.right, IRLiteral) and isinstance(step.right.value, int):
                    return step.right.value > 0
        return False

    comparison_op = ">" if is_negative_step(node.step) else "<"
    cond = self.builder.icmp_signed(comparison_op, loop_var_val, end_val, name="for.cond")
    self.builder.cbranch(cond, body_block, exit_block)

    # Body
    self.builder.position_at_end(body_block)
    for stmt in node.body:
        stmt.accept(self)
    if not self.builder.block.is_terminated:
        self.builder.branch(inc_block)

    # Increment
    self.builder.position_at_end(inc_block)
    loop_var_val = self.builder.load(loop_var_ptr)
    if node.step:
        step_val = node.step.accept(self)
    else:
        step_val = ir.Constant(loop_var_type, 1)
    next_val = self.builder.add(loop_var_val, step_val, name="for.inc")
    self.builder.store(next_val, loop_var_ptr)
    self.builder.branch(cond_block)

    # Pop loop blocks from stack
    self.loop_exit_stack.pop()
    self.loop_continue_stack.pop()

    # Exit
    self.builder.position_at_end(exit_block)

visit_function(node)

Convert IR function to LLVM function.

Parameters:

Name Type Description Default
node IRFunction

IR function to convert

required

Returns:

Type Description
Function

LLVM function with generated body

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_function(self, node: IRFunction) -> ir.Function:
    """Convert IR function to LLVM function.

    Args:
        node: IR function to convert

    Returns:
        LLVM function with generated body
    """
    # Map IRType → llvmlite type
    ret_type = self._convert_type(node.return_type)
    param_types = [self._convert_type(p.ir_type) for p in node.parameters]

    # Create LLVM function type and function
    func_type = ir.FunctionType(ret_type, param_types)
    func = ir.Function(self.module, func_type, node.name)

    # Store in symbol table
    self.func_symtab[node.name] = func
    self.current_function = func

    # Create entry block
    entry_block = func.append_basic_block(name="entry")
    self.builder = ir.IRBuilder(entry_block)

    # Clear variable symbol table for new function
    self.var_symtab = {}

    # Map parameters to LLVM function arguments
    for i, param in enumerate(node.parameters):
        # Allocate stack space for parameter (enables taking address)
        param_ptr = self.builder.alloca(func.args[i].type, name=param.name)
        self.builder.store(func.args[i], param_ptr)
        self.var_symtab[param.name] = param_ptr

    # Generate function body
    for stmt in node.body:
        stmt.accept(self)

    # Add implicit return if missing
    if not self.builder.block.is_terminated:
        if ret_type == ir.VoidType():
            self.builder.ret_void()
        else:
            # Return zero/null as default
            self.builder.ret(ir.Constant(ret_type, 0))

    return func

visit_function_call(node)

Convert IR function call to LLVM call instruction.

Parameters:

Name Type Description Default
node IRFunctionCall

IR function call to convert

required

Returns:

Type Description
CallInstr

LLVM call instruction

Source code in src/multigen/backends/llvm/ir_to_llvm.py
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
def visit_function_call(self, node: IRFunctionCall) -> ir.CallInstr:
    """Convert IR function call to LLVM call instruction.

    Args:
        node: IR function call to convert

    Returns:
        LLVM call instruction
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Handle method calls (from IR builder)
    if node.function_name == "__method_append__":
        # list.append(value) -> vec_int_push, vec_str_push, or vec_vec_int_push
        if len(node.arguments) != 2:
            raise RuntimeError("append() requires exactly 2 arguments (list and value)")

        list_ptr = node.arguments[0].accept(self)  # Already a pointer
        value = node.arguments[1].accept(self)

        # Determine list type based on LLVM types
        from llvmlite import ir as llvm_ir

        list_ptr_type = list_ptr.type

        # Check the pointee type to determine which vec_* type we have
        if isinstance(list_ptr_type, llvm_ir.PointerType):
            pointee_type_str = str(list_ptr_type.pointee)

            if "vec_vec_int" in pointee_type_str:
                # 2D list: vec_vec_int_push(list_ptr, vec_int_ptr)
                vec_push_func = self.runtime.get_function("vec_vec_int_push")
                self.builder.call(vec_push_func, [list_ptr, value], name="")
            elif "vec_str" in pointee_type_str:
                # String list: vec_str_push(list_ptr, char* value)
                vec_push_func = self.runtime.get_function("vec_str_push")
                self.builder.call(vec_push_func, [list_ptr, value], name="")
            else:
                # Integer list (default): vec_int_push(list_ptr, int_value)
                vec_push_func = self.runtime.get_function("vec_int_push")
                self.builder.call(vec_push_func, [list_ptr, value], name="")
        else:
            # Fallback to vec_int
            vec_push_func = self.runtime.get_function("vec_int_push")
            self.builder.call(vec_push_func, [list_ptr, value], name="")

        # Return the pointer (unchanged, since append mutates in place)
        return list_ptr

    elif node.function_name == "__getitem__":
        # list[index] or dict[key] -> vec_*_at or map_*_get
        if len(node.arguments) != 2:
            raise RuntimeError("__getitem__() requires exactly 2 arguments (container and index/key)")

        container_ptr = node.arguments[0].accept(self)  # Already a pointer
        key_or_index = node.arguments[1].accept(self)

        # Determine container type based on LLVM type
        from llvmlite import ir as llvm_ir

        container_type = container_ptr.type

        # Check the pointee type to determine which container we have
        if isinstance(container_type, llvm_ir.PointerType):
            pointee_type_str = str(container_type.pointee)

            if "map_str_int" in pointee_type_str:
                # Dict: map_str_int_get(dict_ptr, key) returns i64
                map_get_func = self.runtime.get_function("map_str_int_get")
                return self.builder.call(map_get_func, [container_ptr, key_or_index], name="dict_get")
            elif "map_int_int" in pointee_type_str:
                # Dict: map_int_int_get(dict_ptr, key) returns i64
                map_get_func = self.runtime.get_function("map_int_int_get")
                return self.builder.call(map_get_func, [container_ptr, key_or_index], name="dict_get")
            elif "vec_vec_int" in pointee_type_str:
                # 2D list: vec_vec_int_at(list_ptr, index) returns vec_int*
                vec_at_func = self.runtime.get_function("vec_vec_int_at")
                return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")
            elif "vec_str" in pointee_type_str:
                # String list: vec_str_at(list_ptr, index) returns char*
                vec_at_func = self.runtime.get_function("vec_str_at")
                return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")
            else:
                # Integer list (default): vec_int_at(list_ptr, index) returns i64
                vec_at_func = self.runtime.get_function("vec_int_at")
                return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")
        else:
            # Fallback to vec_int
            vec_at_func = self.runtime.get_function("vec_int_at")
            return self.builder.call(vec_at_func, [container_ptr, key_or_index], name="list_at")

    elif node.function_name == "__set_get_nth__":
        # set_get_nth_element(set, index) -> element
        if len(node.arguments) != 2:
            raise RuntimeError("__set_get_nth__() requires exactly 2 arguments (set and index)")

        set_ptr = node.arguments[0].accept(self)  # Already a pointer
        index = node.arguments[1].accept(self)

        # Call set_int_get_nth_element(set_ptr, index) returns i64
        get_nth_func = self.runtime.get_function("set_int_get_nth_element")
        return self.builder.call(get_nth_func, [set_ptr, index], name="set_get_nth")

    elif node.function_name == "__setitem__":
        # list[index] = value or dict[key] = value -> vec_*_set or map_*_set
        if len(node.arguments) != 3:
            raise RuntimeError("__setitem__() requires exactly 3 arguments (container, index/key, value)")

        container_ptr = node.arguments[0].accept(self)  # Already a pointer
        key_or_index = node.arguments[1].accept(self)
        value = node.arguments[2].accept(self)

        # Determine container type based on LLVM type
        from llvmlite import ir as llvm_ir

        container_type = container_ptr.type

        # Check the pointee type to determine which container we have
        if isinstance(container_type, llvm_ir.PointerType):
            pointee_type_str = str(container_type.pointee)

            if "map_str_int" in pointee_type_str:
                # Dict: map_str_int_set(dict_ptr, key, value)
                map_set_func = self.runtime.get_function("map_str_int_set")
                return self.builder.call(map_set_func, [container_ptr, key_or_index, value], name="")
            elif "map_int_int" in pointee_type_str:
                # Dict: map_int_int_set(dict_ptr, key, value)
                map_set_func = self.runtime.get_function("map_int_int_set")
                return self.builder.call(map_set_func, [container_ptr, key_or_index, value], name="")
            elif "vec_str" in pointee_type_str:
                # String list: vec_str_set(list_ptr, index, char* value)
                vec_set_func = self.runtime.get_function("vec_str_set")
                return self.builder.call(vec_set_func, [container_ptr, key_or_index, value], name="")
            else:
                # Integer list (default): vec_int_set(list_ptr, index, i64 value)
                vec_set_func = self.runtime.get_function("vec_int_set")
                return self.builder.call(vec_set_func, [container_ptr, key_or_index, value], name="")
        else:
            # Fallback to vec_int
            vec_set_func = self.runtime.get_function("vec_int_set")
            return self.builder.call(vec_set_func, [container_ptr, key_or_index, value], name="")

    elif node.function_name == "__contains__":
        # key in dict -> map_str_int_contains(dict_ptr, key)
        if len(node.arguments) != 2:
            raise RuntimeError("__contains__() requires exactly 2 arguments (container and key)")

        container_ptr = node.arguments[0].accept(self)  # Container pointer
        key = node.arguments[1].accept(self)  # Key to check

        # Determine container type
        from llvmlite import ir as llvm_ir

        container_type = container_ptr.type

        if isinstance(container_type, llvm_ir.PointerType):
            pointee_type_str = str(container_type.pointee)

            if "map_str_int" in pointee_type_str:
                # Dict: map_str_int_contains(dict_ptr, key) returns i32
                map_contains_func = self.runtime.get_function("map_str_int_contains")
                result = self.builder.call(map_contains_func, [container_ptr, key], name="contains_result")
                # Convert i32 result to i1 (bool) by comparing with 0
                return self.builder.icmp_signed("!=", result, ir.Constant(ir.IntType(32), 0), name="contains_bool")
            else:
                # TODO: Add list contains support if needed
                raise NotImplementedError(f"__contains__ not implemented for type {pointee_type_str}")
        else:
            raise NotImplementedError("__contains__ requires a pointer type")

    elif node.function_name == "__method_split__":
        # str.split(delimiter) -> multigen_str_split(str, delimiter)
        # Returns multigen_string_array_t* which we bitcast to vec_str*
        if len(node.arguments) < 1 or len(node.arguments) > 2:
            raise RuntimeError("split() requires 1 or 2 arguments (string and optional delimiter)")

        str_ptr = node.arguments[0].accept(self)  # char* string

        # Get delimiter (default to empty string for whitespace splitting)
        if len(node.arguments) == 2:
            delim_ptr = node.arguments[1].accept(self)
        else:
            # Empty string means split on whitespace
            empty_str = self._create_string_constant("")
            delim_ptr = empty_str

        # Call multigen_str_split (returns multigen_string_array_t*)
        split_func = self.runtime.get_function("multigen_str_split")
        string_array_result = self.builder.call(split_func, [str_ptr, delim_ptr], name="split_result")

        # Bitcast multigen_string_array_t* to vec_str* (same layout: char**, size_t, size_t)
        vec_str_ptr_type = self.runtime.get_vec_str_type().as_pointer()
        vec_str_result = self.builder.bitcast(string_array_result, vec_str_ptr_type, name="split_as_vec_str")
        return vec_str_result

    elif node.function_name == "__method_lower__":
        # str.lower() -> multigen_str_lower(str)
        if len(node.arguments) != 1:
            raise RuntimeError("lower() requires exactly 1 argument (string)")

        str_ptr = node.arguments[0].accept(self)
        lower_func = self.runtime.get_function("multigen_str_lower")
        return self.builder.call(lower_func, [str_ptr], name="lower_result")

    elif node.function_name == "__method_strip__":
        # str.strip() -> multigen_str_strip(str)
        if len(node.arguments) != 1:
            raise RuntimeError("strip() requires exactly 1 argument (string)")

        str_ptr = node.arguments[0].accept(self)
        strip_func = self.runtime.get_function("multigen_str_strip")
        return self.builder.call(strip_func, [str_ptr], name="strip_result")

    elif node.function_name == "__method_upper__":
        # str.upper() -> multigen_str_upper(str)
        if len(node.arguments) != 1:
            raise RuntimeError("upper() requires exactly 1 argument (string)")

        str_ptr = node.arguments[0].accept(self)
        upper_func = self.runtime.get_function("multigen_str_upper")
        return self.builder.call(upper_func, [str_ptr], name="upper_result")

    elif node.function_name == "__method_replace__":
        # str.replace(old, new) -> multigen_str_replace(str, old, new)
        if len(node.arguments) != 3:
            raise RuntimeError("replace() requires exactly 3 arguments (string, old, new)")

        str_ptr = node.arguments[0].accept(self)
        old_ptr = node.arguments[1].accept(self)
        new_ptr = node.arguments[2].accept(self)
        replace_func = self.runtime.get_function("multigen_str_replace")
        return self.builder.call(replace_func, [str_ptr, old_ptr, new_ptr], name="replace_result")

    elif node.function_name == "__method_startswith__":
        # str.startswith(prefix) -> multigen_str_startswith(str, prefix) returns i32
        if len(node.arguments) != 2:
            raise RuntimeError("startswith() requires exactly 2 arguments (string, prefix)")

        str_ptr = node.arguments[0].accept(self)
        prefix_ptr = node.arguments[1].accept(self)
        startswith_func = self.runtime.get_function("multigen_str_startswith")
        result = self.builder.call(startswith_func, [str_ptr, prefix_ptr], name="startswith_result")
        # Convert i32 result to i1 (bool) by comparing with 0
        return self.builder.icmp_signed("!=", result, ir.Constant(ir.IntType(32), 0), name="startswith_bool")

    elif node.function_name == "__method_endswith__":
        # str.endswith(suffix) -> multigen_str_endswith(str, suffix) returns i32
        if len(node.arguments) != 2:
            raise RuntimeError("endswith() requires exactly 2 arguments (string, suffix)")

        str_ptr = node.arguments[0].accept(self)
        suffix_ptr = node.arguments[1].accept(self)
        endswith_func = self.runtime.get_function("multigen_str_endswith")
        result = self.builder.call(endswith_func, [str_ptr, suffix_ptr], name="endswith_result")
        # Convert i32 result to i1 (bool) by comparing with 0
        return self.builder.icmp_signed("!=", result, ir.Constant(ir.IntType(32), 0), name="endswith_bool")

    elif node.function_name == "__method_join__":
        # separator.join(list) -> multigen_str_join(separator, list)
        # Note: list should be vec_str* which has same layout as multigen_string_array_t*
        if len(node.arguments) != 2:
            raise RuntimeError("join() requires exactly 2 arguments (separator, list)")

        sep_ptr = node.arguments[0].accept(self)
        list_ptr = node.arguments[1].accept(self)

        # Bitcast vec_str* to multigen_string_array_t* (same layout: char**, size_t, size_t)
        string_array_ptr_type = self.runtime.get_string_array_type().as_pointer()
        string_array_ptr = self.builder.bitcast(list_ptr, string_array_ptr_type, name="list_as_string_array")

        join_func = self.runtime.get_function("multigen_str_join")
        return self.builder.call(join_func, [sep_ptr, string_array_ptr], name="join_result")

    elif node.function_name == "__method_values__":
        # dict.values() -> create and return vec_int with all values
        if len(node.arguments) != 1:
            raise RuntimeError("values() requires exactly 1 argument (dict)")

        dict_ptr = node.arguments[0].accept(self)
        return self._generate_dict_values_vec(dict_ptr)

    elif node.function_name == "__method_items__":
        # dict.items() - not directly callable, should only be used in for loops
        raise NotImplementedError("dict.items() can only be used in for loops or comprehensions")

    # Handle builtin functions
    elif node.function_name == "len":
        # len() function - use strlen for strings, vec_*_size for lists, map_*_size for dicts
        if len(node.arguments) != 1:
            raise NotImplementedError("len() requires exactly one argument")

        arg = node.arguments[0]
        llvm_arg = arg.accept(self)

        if arg.result_type.base_type == IRDataType.STRING:
            # Use C strlen function
            i8_ptr = ir.IntType(8).as_pointer()
            i64 = ir.IntType(64)
            strlen_func = self._get_or_create_c_function("strlen", i64, [i8_ptr])
            return self.builder.call(strlen_func, [llvm_arg], name="len_tmp")
        elif arg.result_type.base_type == IRDataType.DICT:
            # Use map_*_size function from runtime
            # llvm_arg is already a pointer
            from llvmlite import ir as llvm_ir

            if isinstance(llvm_arg.type, llvm_ir.PointerType):
                pointee_type_str = str(llvm_arg.type.pointee)

                if "map_str_int" in pointee_type_str:
                    map_size_func = self.runtime.get_function("map_str_int_size")
                    return self.builder.call(map_size_func, [llvm_arg], name="len_tmp")
                elif "map_int_int" in pointee_type_str:
                    map_size_func = self.runtime.get_function("map_int_int_size")
                    return self.builder.call(map_size_func, [llvm_arg], name="len_tmp")
                else:
                    raise NotImplementedError(f"len() for dict type {pointee_type_str} not implemented")
            else:
                raise NotImplementedError("len() requires a pointer type for dicts")
        elif arg.result_type.base_type == IRDataType.LIST:
            # Use vec_*_size function from runtime based on element type
            # llvm_arg is already a pointer
            from llvmlite import ir as llvm_ir

            # Check the pointee type to determine which vec_* type we have
            if isinstance(llvm_arg.type, llvm_ir.PointerType):
                pointee_type_str = str(llvm_arg.type.pointee)

                if "vec_vec_int" in pointee_type_str:
                    vec_size_func = self.runtime.get_function("vec_vec_int_size")
                elif "vec_str" in pointee_type_str:
                    vec_size_func = self.runtime.get_function("vec_str_size")
                else:
                    vec_size_func = self.runtime.get_function("vec_int_size")
            else:
                # Fallback to vec_int
                vec_size_func = self.runtime.get_function("vec_int_size")

            return self.builder.call(vec_size_func, [llvm_arg], name="len_tmp")
        elif arg.result_type.base_type == IRDataType.SET:
            # Use set_int_size function from runtime
            # llvm_arg is already a pointer
            set_size_func = self.runtime.get_function("set_int_size")
            return self.builder.call(set_size_func, [llvm_arg], name="len_tmp")
        else:
            raise NotImplementedError(f"len() for type {arg.result_type.base_type} not implemented")

    elif node.function_name == "print":
        # Get or create printf declaration
        arg_types = [arg.result_type for arg in node.arguments]
        printf_func = self._get_or_create_builtin("print", arg_types)

        # Create format string based on argument type
        if len(node.arguments) == 1:
            arg = node.arguments[0]
            if arg.result_type.base_type == IRDataType.INT:
                fmt_bytes = b"%lld\n\x00"  # %lld\n\0 as bytes
            elif arg.result_type.base_type == IRDataType.FLOAT:
                fmt_bytes = b"%f\n\x00"  # %f\n\0 as bytes
            elif arg.result_type.base_type == IRDataType.BOOL:
                fmt_bytes = b"%d\n\x00"  # %d\n\0 as bytes
            elif arg.result_type.base_type == IRDataType.STRING:
                fmt_bytes = b"%s\n\x00"  # %s\n\0 as bytes
            else:
                raise NotImplementedError(f"Print for type {arg.result_type.base_type} not implemented")

            # Create global string constant for format
            fmt_const = ir.Constant(ir.ArrayType(ir.IntType(8), len(fmt_bytes)), bytearray(fmt_bytes))
            fmt_global = ir.GlobalVariable(self.module, fmt_const.type, name=f"fmt_{len(self.module.globals)}")
            fmt_global.linkage = "internal"
            fmt_global.global_constant = True
            fmt_global.initializer = fmt_const

            # Get pointer to the format string
            fmt_ptr = self.builder.gep(fmt_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])

            # Evaluate argument and call printf
            llvm_arg = arg.accept(self)
            return self.builder.call(printf_func, [fmt_ptr, llvm_arg], name="print_tmp")
        else:
            raise NotImplementedError("Print with multiple arguments not implemented")

    # Regular function call
    func = self.func_symtab.get(node.function_name)
    if func is None:
        raise RuntimeError(f"Function '{node.function_name}' not found in symbol table")

    args = [arg.accept(self) for arg in node.arguments]
    return self.builder.call(func, args, name="call_tmp")

visit_if(node)

Convert IR if statement to LLVM basic blocks with branches.

Parameters:

Name Type Description Default
node IRIf

IR if statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_if(self, node: IRIf) -> None:
    """Convert IR if statement to LLVM basic blocks with branches.

    Args:
        node: IR if statement to convert
    """
    if self.builder is None or self.current_function is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Evaluate condition
    cond = node.condition.accept(self)

    # Create basic blocks
    then_block = self.current_function.append_basic_block("if.then")
    else_block = self.current_function.append_basic_block("if.else")
    merge_block = self.current_function.append_basic_block("if.merge")

    # Branch on condition
    self.builder.cbranch(cond, then_block, else_block)

    # Generate then block
    self.builder.position_at_end(then_block)
    for stmt in node.then_body:
        stmt.accept(self)
    if not self.builder.block.is_terminated:
        self.builder.branch(merge_block)

    # Generate else block
    self.builder.position_at_end(else_block)
    for stmt in node.else_body:
        stmt.accept(self)
    if not self.builder.block.is_terminated:
        self.builder.branch(merge_block)

    # Continue at merge point
    self.builder.position_at_end(merge_block)

visit_literal(node)

Convert IR literal to LLVM constant or initialization call.

Parameters:

Name Type Description Default
node IRLiteral

IR literal to convert

required

Returns:

Type Description
Union[Constant, CallInstr]

LLVM constant value or call instruction for complex types

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_literal(self, node: IRLiteral) -> Union[ir.Constant, ir.CallInstr]:
    """Convert IR literal to LLVM constant or initialization call.

    Args:
        node: IR literal to convert

    Returns:
        LLVM constant value or call instruction for complex types
    """
    llvm_type = self._convert_type(node.result_type)

    if node.result_type.base_type == IRDataType.LIST:
        # List literal - allocate and initialize
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Determine element type from IR type annotation
        elem_type = None
        if hasattr(node.result_type, "element_type") and node.result_type.element_type:
            elem_type = node.result_type.element_type.base_type
        elif isinstance(node.value, list) and len(node.value) > 0:
            # Fallback: infer from first element
            first_elem = node.value[0]
            if hasattr(first_elem, "result_type"):
                elem_type = first_elem.result_type.base_type

        # Select appropriate vec_* type based on element type
        is_2d_list = elem_type == IRDataType.LIST

        if elem_type == IRDataType.LIST:
            # 2D list: vec_vec_int
            vec_type = self.runtime.get_vec_vec_int_type()
            vec_init_ptr_func = self.runtime.get_function("vec_vec_int_init_ptr")
            vec_push_func = self.runtime.get_function("vec_vec_int_push")
        elif elem_type == IRDataType.STRING:
            # String list: vec_str
            vec_type = self.runtime.get_vec_str_type()
            vec_init_ptr_func = self.runtime.get_function("vec_str_init_ptr")
            vec_push_func = self.runtime.get_function("vec_str_push")
        else:
            # Default to integer list: vec_int
            vec_type = self.runtime.get_vec_int_type()
            vec_init_ptr_func = self.runtime.get_function("vec_int_init_ptr")
            vec_push_func = self.runtime.get_function("vec_int_push")

        # Allocate space for the vec struct on heap (not stack!)
        # Calculate size of struct using GEP null trick
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()
        null_ptr = ir.Constant(vec_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")

        # Get malloc function and allocate memory
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="list_malloc")

        # Cast i8* to struct pointer
        vec_ptr = self.builder.bitcast(raw_ptr, vec_type.as_pointer(), name="list_tmp")

        # Initialize it by calling vec_init_ptr() which takes a pointer
        self.builder.call(vec_init_ptr_func, [vec_ptr], name="")

        # If list has elements, push them
        if isinstance(node.value, list) and len(node.value) > 0:
            for element_expr in node.value:
                # Visit the element expression to get its LLVM value
                element_val = element_expr.accept(self)

                if is_2d_list:
                    # For 2D lists, element_val is a pointer to vec_int
                    # vec_vec_int_push now takes vec_int by pointer (not by value)
                    self.builder.call(vec_push_func, [vec_ptr, element_val], name="")
                else:
                    # For 1D lists, element_val is an i64
                    self.builder.call(vec_push_func, [vec_ptr, element_val], name="")

        # Return the pointer
        return vec_ptr

    elif node.result_type.base_type == IRDataType.DICT:
        # Dict literal - allocate and initialize
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Check element_type to determine key type (default to int)
        if node.result_type.element_type and node.result_type.element_type.base_type == IRDataType.STRING:
            map_type = self.runtime.get_map_str_int_type()
            map_init_ptr_func = self.runtime.get_function("map_str_int_init_ptr")
        else:
            map_type = self.runtime.get_map_int_int_type()
            map_init_ptr_func = self.runtime.get_function("map_int_int_init_ptr")

        # Allocate space for the map struct on heap
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()
        null_ptr = ir.Constant(map_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")

        # Get malloc function and allocate memory
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="dict_malloc")

        # Cast i8* to struct pointer
        map_ptr = self.builder.bitcast(raw_ptr, map_type.as_pointer(), name="dict_tmp")

        # Initialize it by calling map_init_ptr()
        self.builder.call(map_init_ptr_func, [map_ptr], name="")

        # TODO: If dict has initial key-value pairs, set them here
        # For now, we only support empty dict literals: {}

        # Return the pointer
        return map_ptr

    elif node.result_type.base_type == IRDataType.SET:
        # Set literal - allocate and initialize (typically empty set from set())
        if self.builder is None:
            raise RuntimeError("Builder not initialized - must be inside a function")

        # Get set_int type and init function
        set_type = self.runtime.get_set_int_type()
        set_init_ptr_func = self.runtime.get_function("set_int_init_ptr")

        # Allocate space for the set struct on heap
        i64 = ir.IntType(64)
        i8_ptr = ir.IntType(8).as_pointer()
        null_ptr = ir.Constant(set_type.as_pointer(), None)
        size_gep = self.builder.gep(null_ptr, [ir.Constant(ir.IntType(32), 1)], name="size_gep")
        struct_size = self.builder.ptrtoint(size_gep, i64, name="struct_size")

        # Get malloc function and allocate memory
        malloc_func = self._get_or_create_c_function("malloc", i8_ptr, [i64])
        raw_ptr = self.builder.call(malloc_func, [struct_size], name="set_malloc")

        # Cast i8* to struct pointer
        set_ptr = self.builder.bitcast(raw_ptr, set_type.as_pointer(), name="set_tmp")

        # Initialize it by calling set_init_ptr()
        self.builder.call(set_init_ptr_func, [set_ptr], name="")

        # Return the pointer
        return set_ptr

    elif node.result_type.base_type == IRDataType.INT:
        return ir.Constant(llvm_type, int(node.value))
    elif node.result_type.base_type == IRDataType.FLOAT:
        return ir.Constant(llvm_type, float(node.value))
    elif node.result_type.base_type == IRDataType.BOOL:
        return ir.Constant(llvm_type, 1 if node.value else 0)
    elif node.result_type.base_type == IRDataType.STRING:
        # String literals are stored as global constants
        # Create a null-terminated string
        str_value = str(node.value)
        str_bytes = (str_value + "\0").encode("utf-8")
        str_const = ir.Constant(ir.ArrayType(ir.IntType(8), len(str_bytes)), bytearray(str_bytes))

        # Create global variable for the string
        str_global = ir.GlobalVariable(self.module, str_const.type, name=f"str_{len(self.module.globals)}")
        str_global.linkage = "internal"
        str_global.global_constant = True
        str_global.initializer = str_const

        # Return pointer to the string (i8*)
        if self.builder is not None:
            return self.builder.gep(str_global, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])
        else:
            # During global variable initialization, return the global itself
            return str_global
    elif node.result_type.base_type == IRDataType.VOID:
        # VOID literals shouldn't exist - this is likely a bug in IR generation
        # Return null pointer as workaround
        return ir.Constant(ir.IntType(8).as_pointer(), None)

    raise NotImplementedError(f"Literal type {node.result_type.base_type} not implemented (value={node.value})")

visit_module(node)

Convert IR module to LLVM module.

Parameters:

Name Type Description Default
node IRModule

IR module to convert

required

Returns:

Type Description
Module

LLVM module with generated functions

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_module(self, node: IRModule) -> ir.Module:
    """Convert IR module to LLVM module.

    Args:
        node: IR module to convert

    Returns:
        LLVM module with generated functions
    """
    # Declare runtime library functions first
    self.runtime.declare_all()

    # Generate type declarations first (for structs, etc.)
    for type_decl in node.type_declarations:
        type_decl.accept(self)

    # Generate global variables
    for global_var in node.global_variables:
        global_var.accept(self)

    # Generate functions
    for func in node.functions:
        func.accept(self)

    return self.module

visit_raise(node)

Visit a raise statement node.

Note: LLVM exception raising requires runtime support for unwinding. For now, we simply terminate the program.

Parameters:

Name Type Description Default
node IRRaise

IR raise statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_raise(self, node: IRRaise) -> None:
    """Visit a raise statement node.

    Note: LLVM exception raising requires runtime support for unwinding.
    For now, we simply terminate the program.

    Args:
        node: IR raise statement to convert
    """
    # LLVM exception raising requires runtime unwinding support.
    # For now, call abort() to terminate the program.
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Call the abort() function from C runtime
    abort_func = self.module.globals.get("abort")
    if abort_func is None:
        abort_type = ir.FunctionType(ir.VoidType(), [])
        abort_func = ir.Function(self.module, abort_type, name="abort")

    self.builder.call(abort_func, [])
    self.builder.unreachable()

visit_return(node)

Convert IR return statement to LLVM ret instruction.

Parameters:

Name Type Description Default
node IRReturn

IR return statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_return(self, node: IRReturn) -> None:
    """Convert IR return statement to LLVM ret instruction.

    Args:
        node: IR return statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    if node.value:
        ret_val = node.value.accept(self)
        self.builder.ret(ret_val)
    else:
        self.builder.ret_void()

visit_try(node)

Visit a try/except statement node.

Note: LLVM exception handling is complex and requires proper personality functions and landing pads. For now, we just execute the try body without exception handling support.

Parameters:

Name Type Description Default
node IRTry

IR try statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_try(self, node: IRTry) -> None:
    """Visit a try/except statement node.

    Note: LLVM exception handling is complex and requires proper personality
    functions and landing pads. For now, we just execute the try body
    without exception handling support.

    Args:
        node: IR try statement to convert
    """
    # LLVM exception handling requires EH personality functions and
    # landing pads which are complex to implement. For now, just
    # execute the try body directly.
    for stmt in node.body:
        stmt.accept(self)

visit_type_cast(node)

Convert IR type cast to LLVM cast instruction.

Parameters:

Name Type Description Default
node IRTypeCast

IR type cast to convert

required

Returns:

Type Description
Instruction

LLVM cast instruction

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_type_cast(self, node: IRTypeCast) -> ir.Instruction:
    """Convert IR type cast to LLVM cast instruction.

    Args:
        node: IR type cast to convert

    Returns:
        LLVM cast instruction
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    value = node.value.accept(self)
    source_type = node.value.result_type.base_type
    target_type = node.result_type.base_type

    # INT to FLOAT
    if source_type == IRDataType.INT and target_type == IRDataType.FLOAT:
        llvm_target = self._convert_type(node.result_type)
        return self.builder.sitofp(value, llvm_target, name="cast_tmp")

    # FLOAT to INT
    elif source_type == IRDataType.FLOAT and target_type == IRDataType.INT:
        llvm_target = self._convert_type(node.result_type)
        return self.builder.fptosi(value, llvm_target, name="cast_tmp")

    # INT to BOOL
    elif source_type == IRDataType.INT and target_type == IRDataType.BOOL:
        zero = ir.Constant(ir.IntType(64), 0)
        return self.builder.icmp_signed("!=", value, zero, name="cast_tmp")

    # FLOAT to BOOL
    elif source_type == IRDataType.FLOAT and target_type == IRDataType.BOOL:
        zero = ir.Constant(ir.DoubleType(), 0.0)
        return self.builder.fcmp_ordered("!=", value, zero, name="cast_tmp")

    # BOOL to INT
    elif source_type == IRDataType.BOOL and target_type == IRDataType.INT:
        llvm_target = self._convert_type(node.result_type)
        return self.builder.zext(value, llvm_target, name="cast_tmp")

    # Same type - no cast needed
    elif source_type == target_type:
        return value

    # Unsupported cast
    else:
        raise NotImplementedError(f"Type cast from {source_type} to {target_type} not implemented")

visit_type_declaration(node)

Visit a type declaration node (structs, unions, enums).

Parameters:

Name Type Description Default
node IRTypeDeclaration

IR type declaration to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_type_declaration(self, node: IRTypeDeclaration) -> None:
    """Visit a type declaration node (structs, unions, enums).

    Args:
        node: IR type declaration to convert
    """
    # Type declarations will be implemented when needed for complex types
    # For now, we focus on basic types
    pass

visit_variable(node)

Visit a variable node (used for declarations).

Parameters:

Name Type Description Default
node IRVariable

IR variable to convert

required

Returns:

Type Description
Union[AllocaInstr, GlobalVariable]

LLVM alloca instruction for local variables or GlobalVariable for globals

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_variable(self, node: IRVariable) -> Union[ir.AllocaInstr, ir.GlobalVariable]:
    """Visit a variable node (used for declarations).

    Args:
        node: IR variable to convert

    Returns:
        LLVM alloca instruction for local variables or GlobalVariable for globals
    """
    var_type = self._convert_type(node.ir_type)

    # If builder is None, this is a global variable
    if self.builder is None:
        # Create global variable with initializer
        if node.initial_value is not None:
            initial_value = node.initial_value.accept(self)
        else:
            # Default initialization to zero
            if var_type == ir.IntType(64):
                initial_value = ir.Constant(ir.IntType(64), 0)
            elif var_type == ir.DoubleType():
                initial_value = ir.Constant(ir.DoubleType(), 0.0)
            elif var_type == ir.IntType(1):
                initial_value = ir.Constant(ir.IntType(1), 0)
            else:
                raise NotImplementedError(f"Default initialization for type {var_type} not implemented")

        global_var = ir.GlobalVariable(self.module, var_type, name=node.name)
        global_var.initializer = initial_value
        global_var.linkage = "internal"  # Make it module-private
        self.global_symtab[node.name] = global_var
        return global_var
    else:
        # Local variable - use alloca
        var_ptr = self.builder.alloca(var_type, name=node.name)
        self.var_symtab[node.name] = var_ptr
        return var_ptr

visit_variable_reference(node)

Convert IR variable reference to LLVM load instruction.

Parameters:

Name Type Description Default
node IRVariableReference

IR variable reference to convert

required

Returns:

Type Description
LoadInstr

LLVM load instruction

Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_variable_reference(self, node: IRVariableReference) -> ir.LoadInstr:
    """Convert IR variable reference to LLVM load instruction.

    Args:
        node: IR variable reference to convert

    Returns:
        LLVM load instruction
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Check global variables first, then local
    var_ptr = self.global_symtab.get(node.variable.name)
    if var_ptr is None:
        var_ptr = self.var_symtab.get(node.variable.name)
    if var_ptr is None:
        raise RuntimeError(f"Variable '{node.variable.name}' not found in symbol table")

    return self.builder.load(var_ptr, name=node.variable.name)

visit_while(node)

Convert IR while loop to LLVM loop blocks.

Parameters:

Name Type Description Default
node IRWhile

IR while loop to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_while(self, node: IRWhile) -> None:
    """Convert IR while loop to LLVM loop blocks.

    Args:
        node: IR while loop to convert
    """
    if self.builder is None or self.current_function is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # Create basic blocks
    cond_block = self.current_function.append_basic_block("while.cond")
    body_block = self.current_function.append_basic_block("while.body")
    exit_block = self.current_function.append_basic_block("while.exit")

    # Track loop blocks for break/continue
    self.loop_exit_stack.append(exit_block)
    self.loop_continue_stack.append(cond_block)

    # Jump to condition check
    self.builder.branch(cond_block)

    # Generate condition block
    self.builder.position_at_end(cond_block)
    cond = node.condition.accept(self)
    self.builder.cbranch(cond, body_block, exit_block)

    # Generate body block
    self.builder.position_at_end(body_block)
    for stmt in node.body:
        stmt.accept(self)
    if not self.builder.block.is_terminated:
        self.builder.branch(cond_block)  # Loop back

    # Pop loop blocks from stack
    self.loop_exit_stack.pop()
    self.loop_continue_stack.pop()

    # Continue after loop
    self.builder.position_at_end(exit_block)

visit_with(node)

Visit a with statement node (context manager).

Note: LLVM context managers require file I/O runtime support. This is a stub implementation that visits the body statements. Full file I/O support is planned for future implementation.

Parameters:

Name Type Description Default
node IRWith

IR with statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_with(self, node: IRWith) -> None:
    """Visit a with statement node (context manager).

    Note: LLVM context managers require file I/O runtime support.
    This is a stub implementation that visits the body statements.
    Full file I/O support is planned for future implementation.

    Args:
        node: IR with statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # TODO: Implement proper file I/O with fopen/fclose runtime calls
    # For now, just visit the body statements without actual file handling
    # This allows the rest of the code to compile but file operations won't work
    for stmt in node.body:
        stmt.accept(self)

visit_yield(node)

Visit a yield statement node (generator).

Note: LLVM generator support requires vec runtime support for accumulation. This is a stub implementation that generates a no-op. Full generator support is planned for future implementation.

Parameters:

Name Type Description Default
node IRYield

IR yield statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_yield(self, node: IRYield) -> None:
    """Visit a yield statement node (generator).

    Note: LLVM generator support requires vec runtime support for accumulation.
    This is a stub implementation that generates a no-op.
    Full generator support is planned for future implementation.

    Args:
        node: IR yield statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # TODO: Implement proper generator support with vec_int_push runtime calls
    # For now, evaluate the value expression (for side effects) but discard result
    node.value.accept(self)

visit_yield_from(node)

Visit a yield from statement node (generator).

Stub implementation consistent with visit_yield. Evaluates the iterable expression for side effects only.

Parameters:

Name Type Description Default
node IRYieldFrom

IR yield from statement to convert

required
Source code in src/multigen/backends/llvm/ir_to_llvm.py
def visit_yield_from(self, node: IRYieldFrom) -> None:
    """Visit a yield from statement node (generator).

    Stub implementation consistent with visit_yield.
    Evaluates the iterable expression for side effects only.

    Args:
        node: IR yield from statement to convert
    """
    if self.builder is None:
        raise RuntimeError("Builder not initialized - must be inside a function")

    # TODO: Implement proper generator support with vec extension runtime calls
    # For now, evaluate the iterable expression (for side effects) but discard result
    node.iterable.accept(self)

LLVMBackend

Bases: LanguageBackend

LLVM IR backend for MultiGen.

This backend converts MultiGen's Static IR to LLVM IR, enabling: - Native binary compilation via LLVM - Multiple target architectures (x86, ARM, RISC-V, etc.) - WebAssembly output - JIT compilation (future) - Industrial-strength optimization

Source code in src/multigen/backends/llvm/backend.py
class LLVMBackend(LanguageBackend):
    """LLVM IR backend for MultiGen.

    This backend converts MultiGen's Static IR to LLVM IR, enabling:
    - Native binary compilation via LLVM
    - Multiple target architectures (x86, ARM, RISC-V, etc.)
    - WebAssembly output
    - JIT compilation (future)
    - Industrial-strength optimization
    """

    def __init__(self, preferences: Optional[BackendPreferences] = None) -> None:
        """Initialize the LLVM backend."""
        super().__init__(preferences)
        self._emitter: Optional[AbstractEmitter] = None
        self._factory: Optional[AbstractFactory] = None
        self._builder: Optional[AbstractBuilder] = None
        self._container_system: Optional[AbstractContainerSystem] = None
        self._optimizer: Optional[LLVMOptimizer] = None

    def get_name(self) -> str:
        """Get backend name."""
        return "llvm"

    def get_file_extension(self) -> str:
        """Get file extension for generated files."""
        return ".ll"  # LLVM IR text format

    def get_emitter(self) -> AbstractEmitter:
        """Get the LLVM emitter."""
        if self._emitter is None:
            self._emitter = LLVMEmitter()
        return self._emitter

    def get_factory(self) -> AbstractFactory:
        """Get the LLVM factory."""
        if self._factory is None:
            self._factory = LLVMFactory()
        return self._factory

    def get_builder(self) -> AbstractBuilder:
        """Get the LLVM builder."""
        if self._builder is None:
            self._builder = LLVMBuilder()
        return self._builder

    def get_container_system(self) -> AbstractContainerSystem:
        """Get the LLVM container system."""
        if self._container_system is None:
            self._container_system = LLVMContainerSystem()
        return self._container_system

    def get_optimizer(self) -> AbstractOptimizer:
        """Get the LLVM optimizer.

        Returns an LLVMOptimizer instance that implements the AbstractOptimizer
        interface for LLVM IR optimization with O0-O3 support.

        Returns:
            LLVMOptimizer instance
        """
        if self._optimizer is None:
            self._optimizer = LLVMOptimizer()
        return self._optimizer

    def supports_feature(self, feature: str) -> bool:
        """Check if a feature is supported.

        Args:
            feature: Feature name to check

        Returns:
            True if feature is supported
        """
        supported_features = {
            "functions",
            "variables",
            "arithmetic",
            "control_flow",
            "loops",
            "recursion",
            # Future features:
            # "strings",
            # "containers",
            # "file_io",
            # "jit",
            # "wasm",
        }
        return feature in supported_features

    def get_runtime_dependencies(self) -> list[str]:
        """Get list of runtime dependencies.

        Returns:
            List of runtime library files needed
        """
        # LLVM backend generates self-contained IR for now
        # Future: may need runtime library for strings/containers
        return []

    def validate_compatibility(self, source_code: str) -> tuple[bool, list[str]]:
        """Validate if source code is compatible with LLVM backend.

        Args:
            source_code: Python source code to validate

        Returns:
            Tuple of (is_compatible, list_of_issues)
        """
        issues: list[str] = []

        # For now, assume basic compatibility
        # Future: check for unsupported features (async, generators, etc.)

        return len(issues) == 0, issues

__init__(preferences=None)

Initialize the LLVM backend.

Source code in src/multigen/backends/llvm/backend.py
def __init__(self, preferences: Optional[BackendPreferences] = None) -> None:
    """Initialize the LLVM backend."""
    super().__init__(preferences)
    self._emitter: Optional[AbstractEmitter] = None
    self._factory: Optional[AbstractFactory] = None
    self._builder: Optional[AbstractBuilder] = None
    self._container_system: Optional[AbstractContainerSystem] = None
    self._optimizer: Optional[LLVMOptimizer] = None

get_builder()

Get the LLVM builder.

Source code in src/multigen/backends/llvm/backend.py
def get_builder(self) -> AbstractBuilder:
    """Get the LLVM builder."""
    if self._builder is None:
        self._builder = LLVMBuilder()
    return self._builder

get_container_system()

Get the LLVM container system.

Source code in src/multigen/backends/llvm/backend.py
def get_container_system(self) -> AbstractContainerSystem:
    """Get the LLVM container system."""
    if self._container_system is None:
        self._container_system = LLVMContainerSystem()
    return self._container_system

get_emitter()

Get the LLVM emitter.

Source code in src/multigen/backends/llvm/backend.py
def get_emitter(self) -> AbstractEmitter:
    """Get the LLVM emitter."""
    if self._emitter is None:
        self._emitter = LLVMEmitter()
    return self._emitter

get_factory()

Get the LLVM factory.

Source code in src/multigen/backends/llvm/backend.py
def get_factory(self) -> AbstractFactory:
    """Get the LLVM factory."""
    if self._factory is None:
        self._factory = LLVMFactory()
    return self._factory

get_file_extension()

Get file extension for generated files.

Source code in src/multigen/backends/llvm/backend.py
def get_file_extension(self) -> str:
    """Get file extension for generated files."""
    return ".ll"  # LLVM IR text format

get_name()

Get backend name.

Source code in src/multigen/backends/llvm/backend.py
def get_name(self) -> str:
    """Get backend name."""
    return "llvm"

get_optimizer()

Get the LLVM optimizer.

Returns an LLVMOptimizer instance that implements the AbstractOptimizer interface for LLVM IR optimization with O0-O3 support.

Returns:

Type Description
AbstractOptimizer

LLVMOptimizer instance

Source code in src/multigen/backends/llvm/backend.py
def get_optimizer(self) -> AbstractOptimizer:
    """Get the LLVM optimizer.

    Returns an LLVMOptimizer instance that implements the AbstractOptimizer
    interface for LLVM IR optimization with O0-O3 support.

    Returns:
        LLVMOptimizer instance
    """
    if self._optimizer is None:
        self._optimizer = LLVMOptimizer()
    return self._optimizer

get_runtime_dependencies()

Get list of runtime dependencies.

Returns:

Type Description
list[str]

List of runtime library files needed

Source code in src/multigen/backends/llvm/backend.py
def get_runtime_dependencies(self) -> list[str]:
    """Get list of runtime dependencies.

    Returns:
        List of runtime library files needed
    """
    # LLVM backend generates self-contained IR for now
    # Future: may need runtime library for strings/containers
    return []

supports_feature(feature)

Check if a feature is supported.

Parameters:

Name Type Description Default
feature str

Feature name to check

required

Returns:

Type Description
bool

True if feature is supported

Source code in src/multigen/backends/llvm/backend.py
def supports_feature(self, feature: str) -> bool:
    """Check if a feature is supported.

    Args:
        feature: Feature name to check

    Returns:
        True if feature is supported
    """
    supported_features = {
        "functions",
        "variables",
        "arithmetic",
        "control_flow",
        "loops",
        "recursion",
        # Future features:
        # "strings",
        # "containers",
        # "file_io",
        # "jit",
        # "wasm",
    }
    return feature in supported_features

validate_compatibility(source_code)

Validate if source code is compatible with LLVM backend.

Parameters:

Name Type Description Default
source_code str

Python source code to validate

required

Returns:

Type Description
tuple[bool, list[str]]

Tuple of (is_compatible, list_of_issues)

Source code in src/multigen/backends/llvm/backend.py
def validate_compatibility(self, source_code: str) -> tuple[bool, list[str]]:
    """Validate if source code is compatible with LLVM backend.

    Args:
        source_code: Python source code to validate

    Returns:
        Tuple of (is_compatible, list_of_issues)
    """
    issues: list[str] = []

    # For now, assume basic compatibility
    # Future: check for unsupported features (async, generators, etc.)

    return len(issues) == 0, issues

LLVMCompiler

Compile LLVM IR to machine code using llvmlite.

Source code in src/multigen/backends/llvm/compiler.py
class LLVMCompiler:
    """Compile LLVM IR to machine code using llvmlite."""

    def __init__(self) -> None:
        """Initialize the LLVM compiler."""
        # Initialize native target
        llvm.initialize_native_target()
        llvm.initialize_native_asmprinter()

        # Create target machine for native platform
        target = llvm.Target.from_default_triple()
        self.target_machine = target.create_target_machine()

    def compile_ir_to_object(self, llvm_ir: str, output_path: Optional[str] = None) -> bytes:
        """Compile LLVM IR to object file.

        Args:
            llvm_ir: LLVM IR as string
            output_path: Optional path to write object file

        Returns:
            Object file bytes
        """
        # Parse and verify LLVM IR
        llvm_module = llvm.parse_assembly(llvm_ir)
        llvm_module.verify()

        # Compile to object file
        obj_bytes: bytes = self.target_machine.emit_object(llvm_module)

        # Write to file if path provided
        if output_path:
            Path(output_path).write_bytes(obj_bytes)

        return obj_bytes

    def compile_ir_to_executable(
        self,
        llvm_ir: str,
        output_path: str,
        linker: str = "clang",
        link_args: Optional[list[str]] = None,
        enable_asan: bool = False,
    ) -> bool:
        """Compile LLVM IR to executable.

        Args:
            llvm_ir: LLVM IR as string
            output_path: Path to output executable
            linker: Linker to use (default: clang)
            link_args: Additional linker arguments
            enable_asan: Enable AddressSanitizer for memory error detection

        Returns:
            True if successful, False otherwise
        """
        # Create temporary object file
        with tempfile.NamedTemporaryFile(suffix=".o", delete=False) as obj_file:
            obj_path = obj_file.name
            self.compile_ir_to_object(llvm_ir, obj_path)

        try:
            # Link to executable
            cmd = [linker, obj_path, "-o", output_path]

            # Add ASAN flags if requested
            if enable_asan:
                cmd.extend(["-fsanitize=address", "-g"])

            if link_args:
                cmd.extend(link_args)

            result = subprocess.run(cmd, capture_output=True, text=True)

            if result.returncode != 0:
                raise RuntimeError(f"Linking failed: {result.stderr}")

            return True

        finally:
            # Clean up temporary object file
            Path(obj_path).unlink(missing_ok=True)

    def compile_and_run(
        self,
        llvm_ir: str,
        capture_output: bool = True,
    ) -> subprocess.CompletedProcess[str]:
        """Compile LLVM IR and execute it.

        Args:
            llvm_ir: LLVM IR as string
            capture_output: Whether to capture stdout/stderr

        Returns:
            CompletedProcess with execution results
        """
        # Create temporary executable
        with tempfile.NamedTemporaryFile(suffix="", delete=False) as exe_file:
            exe_path = exe_file.name

        try:
            # Compile to executable
            self.compile_ir_to_executable(llvm_ir, exe_path)

            # Make executable
            Path(exe_path).chmod(0o755)

            # Execute
            result = subprocess.run(
                [exe_path],
                capture_output=capture_output,
                text=True,
            )

            return result

        finally:
            # Clean up temporary executable
            Path(exe_path).unlink(missing_ok=True)

    def get_target_triple(self) -> str:
        """Get the target triple for this compiler.

        Returns:
            Target triple string (e.g., "arm64-apple-darwin24.6.0")
        """
        triple: str = self.target_machine.triple
        return triple

__init__()

Initialize the LLVM compiler.

Source code in src/multigen/backends/llvm/compiler.py
def __init__(self) -> None:
    """Initialize the LLVM compiler."""
    # Initialize native target
    llvm.initialize_native_target()
    llvm.initialize_native_asmprinter()

    # Create target machine for native platform
    target = llvm.Target.from_default_triple()
    self.target_machine = target.create_target_machine()

compile_and_run(llvm_ir, capture_output=True)

Compile LLVM IR and execute it.

Parameters:

Name Type Description Default
llvm_ir str

LLVM IR as string

required
capture_output bool

Whether to capture stdout/stderr

True

Returns:

Type Description
CompletedProcess[str]

CompletedProcess with execution results

Source code in src/multigen/backends/llvm/compiler.py
def compile_and_run(
    self,
    llvm_ir: str,
    capture_output: bool = True,
) -> subprocess.CompletedProcess[str]:
    """Compile LLVM IR and execute it.

    Args:
        llvm_ir: LLVM IR as string
        capture_output: Whether to capture stdout/stderr

    Returns:
        CompletedProcess with execution results
    """
    # Create temporary executable
    with tempfile.NamedTemporaryFile(suffix="", delete=False) as exe_file:
        exe_path = exe_file.name

    try:
        # Compile to executable
        self.compile_ir_to_executable(llvm_ir, exe_path)

        # Make executable
        Path(exe_path).chmod(0o755)

        # Execute
        result = subprocess.run(
            [exe_path],
            capture_output=capture_output,
            text=True,
        )

        return result

    finally:
        # Clean up temporary executable
        Path(exe_path).unlink(missing_ok=True)

compile_ir_to_executable(llvm_ir, output_path, linker='clang', link_args=None, enable_asan=False)

Compile LLVM IR to executable.

Parameters:

Name Type Description Default
llvm_ir str

LLVM IR as string

required
output_path str

Path to output executable

required
linker str

Linker to use (default: clang)

'clang'
link_args Optional[list[str]]

Additional linker arguments

None
enable_asan bool

Enable AddressSanitizer for memory error detection

False

Returns:

Type Description
bool

True if successful, False otherwise

Source code in src/multigen/backends/llvm/compiler.py
def compile_ir_to_executable(
    self,
    llvm_ir: str,
    output_path: str,
    linker: str = "clang",
    link_args: Optional[list[str]] = None,
    enable_asan: bool = False,
) -> bool:
    """Compile LLVM IR to executable.

    Args:
        llvm_ir: LLVM IR as string
        output_path: Path to output executable
        linker: Linker to use (default: clang)
        link_args: Additional linker arguments
        enable_asan: Enable AddressSanitizer for memory error detection

    Returns:
        True if successful, False otherwise
    """
    # Create temporary object file
    with tempfile.NamedTemporaryFile(suffix=".o", delete=False) as obj_file:
        obj_path = obj_file.name
        self.compile_ir_to_object(llvm_ir, obj_path)

    try:
        # Link to executable
        cmd = [linker, obj_path, "-o", output_path]

        # Add ASAN flags if requested
        if enable_asan:
            cmd.extend(["-fsanitize=address", "-g"])

        if link_args:
            cmd.extend(link_args)

        result = subprocess.run(cmd, capture_output=True, text=True)

        if result.returncode != 0:
            raise RuntimeError(f"Linking failed: {result.stderr}")

        return True

    finally:
        # Clean up temporary object file
        Path(obj_path).unlink(missing_ok=True)

compile_ir_to_object(llvm_ir, output_path=None)

Compile LLVM IR to object file.

Parameters:

Name Type Description Default
llvm_ir str

LLVM IR as string

required
output_path Optional[str]

Optional path to write object file

None

Returns:

Type Description
bytes

Object file bytes

Source code in src/multigen/backends/llvm/compiler.py
def compile_ir_to_object(self, llvm_ir: str, output_path: Optional[str] = None) -> bytes:
    """Compile LLVM IR to object file.

    Args:
        llvm_ir: LLVM IR as string
        output_path: Optional path to write object file

    Returns:
        Object file bytes
    """
    # Parse and verify LLVM IR
    llvm_module = llvm.parse_assembly(llvm_ir)
    llvm_module.verify()

    # Compile to object file
    obj_bytes: bytes = self.target_machine.emit_object(llvm_module)

    # Write to file if path provided
    if output_path:
        Path(output_path).write_bytes(obj_bytes)

    return obj_bytes

get_target_triple()

Get the target triple for this compiler.

Returns:

Type Description
str

Target triple string (e.g., "arm64-apple-darwin24.6.0")

Source code in src/multigen/backends/llvm/compiler.py
def get_target_triple(self) -> str:
    """Get the target triple for this compiler.

    Returns:
        Target triple string (e.g., "arm64-apple-darwin24.6.0")
    """
    triple: str = self.target_machine.triple
    return triple

Backend Selection

Backends are selected via the target_language parameter:

from multigen.pipeline import PipelineConfig

# C backend
config = PipelineConfig(target_language="c")

# Rust backend
config = PipelineConfig(target_language="rust")

Available backends: c, cpp, rust, go, haskell, ocaml, llvm

Extending Backends

To add a new backend:

  1. Subclass LanguageBackend
  2. Implement required abstract methods
  3. Create backend-specific emitter, factory, builder
  4. Register in multigen.backends.registry

See CLAUDE.md for detailed architecture notes.