Support multiple components in single file with scoped signals
- Update generate_llvm_ir() to accept list of component dicts - Process each component with own signal/handler state - Scope signal globals with component name prefix (@signal_ComponentName_signalName) - Add _signal_global() helper for scoped signal names - Update tests for scoped signal names - All tests passing (30/30)
This commit is contained in:
1 parent
0063a8536c
commit
74ff1e7e9f
6 files changed
+53
-30
No files matched your search
Binary file not shown.
@@ -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
|
||||
|
||||
Reference in new issue
Block a user