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
|
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.
|
Generate LLVM IR from Ritz IR with Signal support.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ir: Component IR dictionary
|
ir: Component IR dictionary or list of component dictionaries
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
LLVM IR source code with signals stored in WASM memory
|
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:
|
class SignalLLVMIRGenerator:
|
||||||
"""Generates LLVM IR with reactive Signal support."""
|
"""Generates LLVM IR with reactive Signal support."""
|
||||||
|
|
||||||
def __init__(self, ir: dict[str, Any]):
|
def __init__(self, ir: dict[str, Any] | list[dict[str, Any]]):
|
||||||
self.ir = ir
|
# Normalize to list for uniform processing
|
||||||
|
self.components = ir if isinstance(ir, list) else [ir]
|
||||||
self.lines: list[str] = []
|
self.lines: list[str] = []
|
||||||
self.strings: dict[str, str] = {} # value -> global name
|
self.strings: dict[str, str] = {} # value -> global name
|
||||||
self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index}
|
self.signals: dict[str, dict[str, Any]] = {} # signal_name -> {initial, index}
|
||||||
@@ -33,25 +34,45 @@ class SignalLLVMIRGenerator:
|
|||||||
# Track signal-to-DOM bindings for updates
|
# Track signal-to-DOM bindings for updates
|
||||||
self.signal_dom_bindings: dict[str, list[str]] = {} # signal_name -> [dom_node_ids]
|
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:
|
def generate(self) -> str:
|
||||||
"""Generate complete LLVM IR module with signals."""
|
"""Generate complete LLVM IR module with signals."""
|
||||||
# First pass: collect signals and handlers
|
# Emit shared header and function declarations once
|
||||||
self._collect_signals_and_handlers()
|
|
||||||
|
|
||||||
# Generate IR
|
|
||||||
self._emit_header()
|
self._emit_header()
|
||||||
self._emit_signal_globals()
|
|
||||||
self._emit_function_declarations()
|
self._emit_function_declarations()
|
||||||
self._emit_helper_functions()
|
|
||||||
self._emit_event_handlers()
|
# Process each component
|
||||||
self._emit_component(self.ir)
|
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
|
# Prepend string constants
|
||||||
return self._finalize_with_strings()
|
return self._finalize_with_strings()
|
||||||
|
|
||||||
def _collect_signals_and_handlers(self) -> None:
|
def _collect_signals_and_handlers(self, component: dict[str, Any]) -> None:
|
||||||
"""Collect all signals and event handlers from IR."""
|
"""Collect all signals and event handlers from component IR."""
|
||||||
for stmt in self.ir["body"]:
|
for stmt in component["body"]:
|
||||||
if stmt["type"] == "signal":
|
if stmt["type"] == "signal":
|
||||||
signal_id = self.next_signal_id
|
signal_id = self.next_signal_id
|
||||||
self.next_signal_id += 1
|
self.next_signal_id += 1
|
||||||
@@ -65,19 +86,21 @@ class SignalLLVMIRGenerator:
|
|||||||
def _emit_header(self) -> None:
|
def _emit_header(self) -> None:
|
||||||
"""Emit module header."""
|
"""Emit module header."""
|
||||||
self.lines.append("; Generated by Ritz Framework with Signals")
|
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("")
|
self.lines.append("")
|
||||||
|
|
||||||
def _emit_signal_globals(self) -> None:
|
def _emit_signal_globals(self, component: dict[str, Any]) -> None:
|
||||||
"""Emit global variables for signals."""
|
"""Emit global variables for signals in this component."""
|
||||||
if not self.signals:
|
if not self.signals:
|
||||||
return
|
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():
|
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)
|
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("")
|
self.lines.append("")
|
||||||
|
|
||||||
def _emit_function_declarations(self) -> None:
|
def _emit_function_declarations(self) -> None:
|
||||||
@@ -105,7 +128,7 @@ class SignalLLVMIRGenerator:
|
|||||||
for signal_name in self.signals:
|
for signal_name in self.signals:
|
||||||
self.lines.append(f"define i32 @get_{signal_name}() {{")
|
self.lines.append(f"define i32 @get_{signal_name}() {{")
|
||||||
self.lines.append("entry:")
|
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(" ret i32 %val")
|
||||||
self.lines.append("}")
|
self.lines.append("}")
|
||||||
self.lines.append("")
|
self.lines.append("")
|
||||||
@@ -114,7 +137,7 @@ class SignalLLVMIRGenerator:
|
|||||||
for signal_name in self.signals:
|
for signal_name in self.signals:
|
||||||
self.lines.append(f"define void @set_{signal_name}(i32 %newval) {{")
|
self.lines.append(f"define void @set_{signal_name}(i32 %newval) {{")
|
||||||
self.lines.append("entry:")
|
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(" ; TODO: Trigger DOM update callback")
|
||||||
self.lines.append(" ret void")
|
self.lines.append(" ret void")
|
||||||
self.lines.append("}")
|
self.lines.append("}")
|
||||||
@@ -144,7 +167,7 @@ class SignalLLVMIRGenerator:
|
|||||||
if signal_name in self.signals:
|
if signal_name in self.signals:
|
||||||
# Load current value
|
# Load current value
|
||||||
load_var = self._new_var()
|
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
|
# Perform operation
|
||||||
result_var = self._new_var()
|
result_var = self._new_var()
|
||||||
@@ -153,7 +176,7 @@ class SignalLLVMIRGenerator:
|
|||||||
self.lines.append(f" %{result_var} = add i32 %{load_var}, {value}")
|
self.lines.append(f" %{result_var} = add i32 %{load_var}, {value}")
|
||||||
|
|
||||||
# Store back
|
# 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
|
# TODO: Trigger DOM updates for this signal
|
||||||
self.lines.append(f" ; TODO: Update DOM nodes bound to {signal_name}")
|
self.lines.append(f" ; TODO: Update DOM nodes bound to {signal_name}")
|
||||||
@@ -239,7 +262,7 @@ class SignalLLVMIRGenerator:
|
|||||||
signal_name = expr["id"]
|
signal_name = expr["id"]
|
||||||
if signal_name in self.signals:
|
if signal_name in self.signals:
|
||||||
load_var = self._new_var()
|
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
|
return load_var
|
||||||
else:
|
else:
|
||||||
# Unknown variable
|
# Unknown variable
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -21,8 +21,8 @@ def Toggle():
|
|||||||
ir = parse_component(source)
|
ir = parse_component(source)
|
||||||
llvm_ir = generate_llvm_ir(ir)
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
# Should have signal global
|
# Should have signal global (scoped to component)
|
||||||
assert "@signal_visible = global i32 1" in llvm_ir
|
assert "@signal_Toggle_visible = global i32 1" in llvm_ir
|
||||||
|
|
||||||
# Should have conditional branch
|
# Should have conditional branch
|
||||||
assert "br i1" in llvm_ir
|
assert "br i1" in llvm_ir
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ def test_signal_with_fstring():
|
|||||||
ir = parse_component(source)
|
ir = parse_component(source)
|
||||||
llvm_ir = generate_llvm_ir(ir)
|
llvm_ir = generate_llvm_ir(ir)
|
||||||
|
|
||||||
# Should have signal storage
|
# Should have signal storage (scoped to component)
|
||||||
assert "@signal_count" in llvm_ir
|
assert "@signal_Counter_count" in llvm_ir
|
||||||
|
|
||||||
# Should have getter function
|
# Should have getter function
|
||||||
assert "get_count" in llvm_ir
|
assert "get_count" in llvm_ir
|
||||||
|
|||||||
Reference in new issue
Block a user