diff --git a/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc b/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc index 435bdd9..ec28abc 100644 Binary files a/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc and b/ritz/__pycache__/llvm_codegen_signals.cpython-313.pyc differ diff --git a/ritz/llvm_codegen_signals.py b/ritz/llvm_codegen_signals.py index 3f89362..a40c95f 100644 --- a/ritz/llvm_codegen_signals.py +++ b/ritz/llvm_codegen_signals.py @@ -3,12 +3,12 @@ from typing import Any -def generate_llvm_ir(ir: dict[str, Any]) -> str: +def generate_llvm_ir(ir: dict[str, Any] | list[dict[str, Any]]) -> str: """ Generate LLVM IR from Ritz IR with Signal support. Args: - ir: Component IR dictionary + ir: Component IR dictionary or list of component dictionaries Returns: LLVM IR source code with signals stored in WASM memory @@ -20,8 +20,9 @@ def generate_llvm_ir(ir: dict[str, Any]) -> str: class SignalLLVMIRGenerator: """Generates LLVM IR with reactive Signal support.""" - def __init__(self, ir: dict[str, Any]): - self.ir = ir + def __init__(self, ir: dict[str, Any] | list[dict[str, Any]]): + # Normalize to list for uniform processing + self.components = ir if isinstance(ir, list) else [ir] self.lines: list[str] = [] self.strings: dict[str, str] = {} # value -> global name self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index} @@ -33,25 +34,45 @@ class SignalLLVMIRGenerator: # Track signal-to-DOM bindings for updates self.signal_dom_bindings: dict[str, list[str]] = {} # signal_name -> [dom_node_ids] + # Track current component for signal scoping + self.current_component = None + + def _signal_global(self, signal_name: str) -> str: + """Get the scoped global name for a signal.""" + if self.current_component: + return f"@signal_{self.current_component['name']}_{signal_name}" + return f"@signal_{signal_name}" + def generate(self) -> str: """Generate complete LLVM IR module with signals.""" - # First pass: collect signals and handlers - self._collect_signals_and_handlers() - - # Generate IR + # Emit shared header and function declarations once self._emit_header() - self._emit_signal_globals() self._emit_function_declarations() - self._emit_helper_functions() - self._emit_event_handlers() - self._emit_component(self.ir) + + # Process each component + for component in self.components: + # Set current component for signal scoping + self.current_component = component + + # Reset component-specific state + self.signals = {} + self.event_handlers = [] + + # Collect signals and handlers for this component + self._collect_signals_and_handlers(component) + + # Generate component-specific code + self._emit_signal_globals(component) + self._emit_helper_functions() + self._emit_event_handlers() + self._emit_component(component) # Prepend string constants return self._finalize_with_strings() - def _collect_signals_and_handlers(self) -> None: - """Collect all signals and event handlers from IR.""" - for stmt in self.ir["body"]: + def _collect_signals_and_handlers(self, component: dict[str, Any]) -> None: + """Collect all signals and event handlers from component IR.""" + for stmt in component["body"]: if stmt["type"] == "signal": signal_id = self.next_signal_id self.next_signal_id += 1 @@ -65,19 +86,21 @@ class SignalLLVMIRGenerator: def _emit_header(self) -> None: """Emit module header.""" self.lines.append("; Generated by Ritz Framework with Signals") - self.lines.append(f'; Component: {self.ir["name"]}') + component_names = [c["name"] for c in self.components] + self.lines.append(f'; Components: {", ".join(component_names)}') self.lines.append("") - def _emit_signal_globals(self) -> None: - """Emit global variables for signals.""" + def _emit_signal_globals(self, component: dict[str, Any]) -> None: + """Emit global variables for signals in this component.""" if not self.signals: return - self.lines.append("; Signal storage (reactive state in WASM memory)") + component_name = component["name"] + self.lines.append(f"; Signal storage for {component_name} (reactive state in WASM memory)") for signal_name, signal_info in self.signals.items(): - # Each signal is a global i32 + # Each signal is a global i32, scoped to component to avoid collisions initial_value = signal_info["initial"].get("value", 0) - self.lines.append(f"@signal_{signal_name} = global i32 {initial_value}") + self.lines.append(f"@signal_{component_name}_{signal_name} = global i32 {initial_value}") self.lines.append("") def _emit_function_declarations(self) -> None: @@ -105,7 +128,7 @@ class SignalLLVMIRGenerator: for signal_name in self.signals: self.lines.append(f"define i32 @get_{signal_name}() {{") self.lines.append("entry:") - self.lines.append(f" %val = load i32, i32* @signal_{signal_name}") + self.lines.append(f" %val = load i32, i32* {self._signal_global(signal_name)}") self.lines.append(" ret i32 %val") self.lines.append("}") self.lines.append("") @@ -114,7 +137,7 @@ class SignalLLVMIRGenerator: for signal_name in self.signals: self.lines.append(f"define void @set_{signal_name}(i32 %newval) {{") self.lines.append("entry:") - self.lines.append(f" store i32 %newval, i32* @signal_{signal_name}") + self.lines.append(f" store i32 %newval, i32* {self._signal_global(signal_name)}") self.lines.append(" ; TODO: Trigger DOM update callback") self.lines.append(" ret void") self.lines.append("}") @@ -144,7 +167,7 @@ class SignalLLVMIRGenerator: if signal_name in self.signals: # Load current value load_var = self._new_var() - self.lines.append(f" %{load_var} = load i32, i32* @signal_{signal_name}") + self.lines.append(f" %{load_var} = load i32, i32* {self._signal_global(signal_name)}") # Perform operation result_var = self._new_var() @@ -153,7 +176,7 @@ class SignalLLVMIRGenerator: self.lines.append(f" %{result_var} = add i32 %{load_var}, {value}") # Store back - self.lines.append(f" store i32 %{result_var}, i32* @signal_{signal_name}") + self.lines.append(f" store i32 %{result_var}, i32* {self._signal_global(signal_name)}") # TODO: Trigger DOM updates for this signal self.lines.append(f" ; TODO: Update DOM nodes bound to {signal_name}") @@ -239,7 +262,7 @@ class SignalLLVMIRGenerator: signal_name = expr["id"] if signal_name in self.signals: load_var = self._new_var() - self.lines.append(f" %{load_var} = load i32, i32* @signal_{signal_name}") + self.lines.append(f" %{load_var} = load i32, i32* {self._signal_global(signal_name)}") return load_var else: # Unknown variable diff --git a/tests/__pycache__/test_llvm_codegen_conditional.cpython-313-pytest-8.4.2.pyc b/tests/__pycache__/test_llvm_codegen_conditional.cpython-313-pytest-8.4.2.pyc index 64f1643..b45fb96 100644 Binary files a/tests/__pycache__/test_llvm_codegen_conditional.cpython-313-pytest-8.4.2.pyc and b/tests/__pycache__/test_llvm_codegen_conditional.cpython-313-pytest-8.4.2.pyc differ diff --git a/tests/__pycache__/test_signal_codegen.cpython-313-pytest-8.4.2.pyc b/tests/__pycache__/test_signal_codegen.cpython-313-pytest-8.4.2.pyc index 872ccd7..2c1f93f 100644 Binary files a/tests/__pycache__/test_signal_codegen.cpython-313-pytest-8.4.2.pyc and b/tests/__pycache__/test_signal_codegen.cpython-313-pytest-8.4.2.pyc differ diff --git a/tests/test_llvm_codegen_conditional.py b/tests/test_llvm_codegen_conditional.py index 188f924..f18bc1c 100644 --- a/tests/test_llvm_codegen_conditional.py +++ b/tests/test_llvm_codegen_conditional.py @@ -21,8 +21,8 @@ def Toggle(): ir = parse_component(source) llvm_ir = generate_llvm_ir(ir) - # Should have signal global - assert "@signal_visible = global i32 1" in llvm_ir + # Should have signal global (scoped to component) + assert "@signal_Toggle_visible = global i32 1" in llvm_ir # Should have conditional branch assert "br i1" in llvm_ir diff --git a/tests/test_signal_codegen.py b/tests/test_signal_codegen.py index 66fcfff..1c86975 100644 --- a/tests/test_signal_codegen.py +++ b/tests/test_signal_codegen.py @@ -55,8 +55,8 @@ def test_signal_with_fstring(): ir = parse_component(source) llvm_ir = generate_llvm_ir(ir) - # Should have signal storage - assert "@signal_count" in llvm_ir + # Should have signal storage (scoped to component) + assert "@signal_Counter_count" in llvm_ir # Should have getter function assert "get_count" in llvm_ir