Skip to content

Commit df52dd4

Browse files
Handle globals
1 parent d00c96f commit df52dd4

5 files changed

Lines changed: 206 additions & 34 deletions

File tree

multilingualprogramming/codegen/wat_generator.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -369,29 +369,33 @@ def _gen_stmt(self, stmt, indent: str): # noqa: C901 # pylint: disable=too-man
369369
self._emit(f"{indent};; unpacking declaration lowered")
370370
else:
371371
name = _name(stmt.name)
372-
self._locals.add(name)
373372
self._emit(f"{indent};; let {name} = ...")
374373
self._gen_expr(stmt.value, indent)
375-
self._emit(f"{indent}local.set ${self._wat_symbol(name)}")
374+
if not self._is_module_global(name):
375+
self._locals.add(name)
376+
self._emit_name_set(name, indent)
376377
self._update_assignment_tracking(name, stmt.value, indent)
377378

378379
elif isinstance(stmt, Assignment):
379380
target = stmt.target
380381
if isinstance(target, Identifier):
381382
name = target.name
382-
self._locals.add(name)
383383
op = stmt.op
384384
if op == "=":
385385
self._emit(f"{indent};; {name} = ...")
386386
self._gen_expr(stmt.value, indent)
387-
self._emit(f"{indent}local.set ${self._wat_symbol(name)}")
387+
if not self._is_module_global(name):
388+
self._locals.add(name)
389+
self._emit_name_set(name, indent)
388390
self._update_assignment_tracking(name, stmt.value, indent)
389391
else:
390392
# Compound assignment: a op= b
391393
self._emit(f"{indent};; {name} {op} ...")
392-
self._emit(f"{indent}local.get ${self._wat_symbol(name)}")
394+
if not self._is_module_global(name):
395+
self._locals.add(name)
396+
self._emit_name_get(name, indent)
393397
self._gen_augmented_op(op, stmt.value, indent)
394-
self._emit(f"{indent}local.set ${self._wat_symbol(name)}")
398+
self._emit_name_set(name, indent)
395399
self._clear_assignment_tracking(name)
396400
elif (isinstance(target, AttributeAccess)
397401
and isinstance(target.obj, Identifier)):
@@ -457,15 +461,16 @@ def _gen_stmt(self, stmt, indent: str): # noqa: C901 # pylint: disable=too-man
457461
elif isinstance(stmt, AnnAssignment):
458462
if isinstance(stmt.target, Identifier):
459463
name = stmt.target.name
460-
self._locals.add(name)
461464
self._emit(f"{indent};; annotated assignment {name}: ...")
462465
if stmt.value is None:
463466
self._emit(f"{indent}f64.const 0")
464467
self._clear_assignment_tracking(name)
465468
else:
466469
self._gen_expr(stmt.value, indent)
467470
self._update_assignment_tracking(name, stmt.value, indent)
468-
self._emit(f"{indent}local.set ${self._wat_symbol(name)}")
471+
if not self._is_module_global(name):
472+
self._locals.add(name)
473+
self._emit_name_set(name, indent)
469474
else:
470475
self._emit(f"{indent};; annotated assignment with complex target (nop in WAT)")
471476

@@ -478,9 +483,10 @@ def _gen_stmt(self, stmt, indent: str): # noqa: C901 # pylint: disable=too-man
478483
for target in stmt.targets:
479484
if isinstance(target, Identifier):
480485
name = target.name
481-
self._locals.add(name)
482486
self._emit(f"{indent}local.get ${self._wat_symbol(tmp_name)}")
483-
self._emit(f"{indent}local.set ${self._wat_symbol(name)}")
487+
if not self._is_module_global(name):
488+
self._locals.add(name)
489+
self._emit_name_set(name, indent)
484490
self._update_assignment_tracking(name, stmt.value, indent)
485491
else:
486492
self._emit(
@@ -686,38 +692,38 @@ def _gen_stmt(self, stmt, indent: str): # noqa: C901 # pylint: disable=too-man
686692
elif (isinstance(expr.func, AttributeAccess)
687693
and expr.func.attr == "append"
688694
and isinstance(expr.func.obj, Identifier)
689-
and expr.func.obj.name in self._list_locals
695+
and self._is_tracked_list_name(expr.func.obj.name)
690696
and len(expr.args) == 1):
691697
# lst.append(x) → allocate new list, update local
692698
obj_name = expr.func.obj.name
693699
self._emit(f"{indent};; {obj_name}.append(x)")
694-
self._gen_expr(expr.func.obj, indent)
700+
self._emit_name_get(obj_name, indent)
695701
self._gen_expr(expr.args[0], indent)
696702
self._emit(f"{indent}call $__list_append")
697-
self._emit(f"{indent}local.set ${self._wat_symbol(obj_name)}")
703+
self._emit_name_set(obj_name, indent)
698704
elif (isinstance(expr.func, AttributeAccess)
699705
and expr.func.attr == "pop"
700706
and isinstance(expr.func.obj, Identifier)
701-
and expr.func.obj.name in self._list_locals
707+
and self._is_tracked_list_name(expr.func.obj.name)
702708
and not expr.args):
703709
# lst.pop() statement — result discarded
704710
obj_name = expr.func.obj.name
705711
self._emit(f"{indent};; {obj_name}.pop() (result discarded)")
706-
self._gen_expr(expr.func.obj, indent)
712+
self._emit_name_get(obj_name, indent)
707713
self._emit(f"{indent}call $__list_pop")
708714
self._emit(f"{indent}drop")
709715
elif (isinstance(expr.func, AttributeAccess)
710716
and expr.func.attr == "extend"
711717
and isinstance(expr.func.obj, Identifier)
712-
and expr.func.obj.name in self._list_locals
718+
and self._is_tracked_list_name(expr.func.obj.name)
713719
and len(expr.args) == 1):
714720
# lst.extend(other) → allocate new list, update local
715721
obj_name = expr.func.obj.name
716722
self._emit(f"{indent};; {obj_name}.extend(other)")
717-
self._gen_expr(expr.func.obj, indent)
723+
self._emit_name_get(obj_name, indent)
718724
self._gen_expr(expr.args[0], indent)
719725
self._emit(f"{indent}call $__list_extend")
720-
self._emit(f"{indent}local.set ${self._wat_symbol(obj_name)}")
726+
self._emit_name_set(obj_name, indent)
721727
else:
722728
# Closure, constructor, builtin, or other non-WAT callable
723729
self._emit(f"{indent};; unsupported call: {fname}(...) — not a WAT function")
@@ -1069,8 +1075,7 @@ def _gen_expr(self, node, indent: str): # noqa: C901 # pylint: disable=too-man
10691075
self._emit(f"{indent}f64.const 0 ;; unsupported expr: DictLiteral")
10701076

10711077
elif isinstance(node, Identifier):
1072-
if node.name in self._locals:
1073-
self._emit(f"{indent}local.get ${self._wat_symbol(node.name)}")
1078+
if self._emit_name_get(node.name, indent):
10741079
if node.name in self._string_len_locals:
10751080
len_local = self._string_len_locals[node.name]
10761081
self._emit(f"{indent}local.get ${self._wat_symbol(len_local)}")
@@ -1792,11 +1797,11 @@ def _gen_expr(self, node, indent: str): # noqa: C901 # pylint: disable=too-man
17921797
elif isinstance(node, IndexAccess):
17931798
obj = node.obj
17941799
if isinstance(obj, Identifier) and (
1795-
obj.name in self._list_locals or obj.name in self._tuple_locals
1800+
self._is_tracked_list_name(obj.name) or self._is_tracked_tuple_name(obj.name)
17961801
):
17971802
# list[i] / tuple[i] → load from base + 8 + i*8
17981803
self._emit(f"{indent};; {obj.name}[i]")
1799-
self._emit(f"{indent}local.get ${self._wat_symbol(obj.name)}")
1804+
self._emit_name_get(obj.name, indent)
18001805
self._emit(f"{indent}i32.trunc_f64_u")
18011806
self._gen_expr(node.index, indent)
18021807
self._emit(f"{indent}i32.trunc_f64_u")

multilingualprogramming/codegen/wat_generator_core.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ def _build_module(self) -> str:
7878
lines.append(
7979
' (global $__last_exc_code (export "__last_exception_code") (mut i32) (i32.const 0))'
8080
)
81+
for global_name in sorted(getattr(self, "_module_global_names", set())):
82+
lines.append(
83+
f' (global ${self._wat_symbol(global_name)} (mut f64) (f64.const 0))'
84+
)
8185
if self._lambda_table:
8286
n = len(self._lambda_table)
8387
lines.append(f' (table {n} funcref)')

multilingualprogramming/codegen/wat_generator_oop.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ def _emit_function(self, func_def: FunctionDef, emitted_name: str | None = None)
339339
func_name = emitted_name or _name(func_def.name)
340340
param_names = _real_params(func_def)
341341
self._locals = set(param_names)
342+
self._list_locals.update(self._func_param_list_names.get(func_name, set()))
342343

343344
self._gen_stmts(func_def.body, " ")
344345
body_instrs = list(self._instrs)

multilingualprogramming/codegen/wat_generator_orchestrator.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,23 @@
77
"""Orchestration/state helpers for the WAT generator."""
88

99
from multilingualprogramming.core.ir_nodes import IRProgram
10-
from multilingualprogramming.parser.ast_nodes import CallExpr, ClassDef, FunctionDef
10+
from multilingualprogramming.parser.ast_nodes import (
11+
AnnAssignment,
12+
Assignment,
13+
AttributeAccess,
14+
CallExpr,
15+
ChainedAssignment,
16+
ClassDef,
17+
FunctionDef,
18+
GlobalStatement,
19+
Identifier,
20+
IndexAccess,
21+
VariableDeclaration,
22+
)
1123

1224
from multilingualprogramming.codegen.wat_ir_adapter import lower_ir_to_wat_ast
1325
from multilingualprogramming.codegen.wat_generator_support import (
26+
_LEN_NAMES,
1427
_extract_render_mode,
1528
_name,
1629
_real_params,
@@ -29,6 +42,7 @@ def generate(self, program, *, wasm_target: str = "browser") -> str:
2942
program = lower_ir_to_wat_ast(program)
3043

3144
funcs, classes, top = self._split_program_sections(program)
45+
self._collect_module_globals(funcs, classes, top)
3246
self._collect_function_metadata(funcs)
3347
self._collect_class_lowering(classes)
3448
self._collect_import_aliases(top)
@@ -58,6 +72,7 @@ def _collect_function_metadata(self, funcs: list[FunctionDef]) -> None:
5872
fname = _name(func.name)
5973
self._func_real_params[fname] = _real_params(func)
6074
self._func_render_modes[fname] = _extract_render_mode(func)
75+
self._func_param_list_names[fname] = _infer_list_like_params(func)
6176
if self._returns_string_like(func):
6277
self._string_return_funcs.add(fname)
6378

@@ -96,6 +111,19 @@ def _save_func_state(self):
96111
self._reset_func_state()
97112
return saved
98113

114+
def _collect_module_globals(self, funcs: list, classes: list, top: list) -> None:
115+
"""Record module-scoped identifiers that must lower to mutable WASM globals."""
116+
for stmt in top:
117+
_record_module_global_assignment(self, stmt)
118+
for func in funcs:
119+
for name in _find_declared_global_names(func):
120+
self._module_global_names.add(name)
121+
for cls in classes:
122+
for member in getattr(cls, "body", []) or []:
123+
if isinstance(member, FunctionDef):
124+
for name in _find_declared_global_names(member):
125+
self._module_global_names.add(name)
126+
99127

100128
def _reset_generator_state(generator) -> None:
101129
"""Reset all mutable per-module generation state for *generator*."""
@@ -150,6 +178,102 @@ def _reset_generator_state(generator) -> None:
150178
"_property_getters": {},
151179
"_class_ids": {},
152180
"_dispatch_func_names": {},
181+
"_module_global_names": set(),
182+
"_module_global_list_names": set(),
183+
"_module_global_tuple_names": set(),
184+
"_module_global_dict_names": set(),
185+
"_func_param_list_names": {},
153186
}
154187
for name, value in state.items():
155188
setattr(generator, name, value)
189+
190+
191+
def _record_module_global_assignment(generator, stmt) -> None:
192+
"""Update tracked module-global names from a top-level assignment-like node."""
193+
pairs = []
194+
if isinstance(stmt, VariableDeclaration) and isinstance(stmt.name, Identifier):
195+
pairs.append((stmt.name.name, stmt.value))
196+
elif isinstance(stmt, Assignment) and isinstance(stmt.target, Identifier):
197+
pairs.append((stmt.target.name, stmt.value))
198+
elif isinstance(stmt, AnnAssignment) and isinstance(stmt.target, Identifier):
199+
pairs.append((stmt.target.name, stmt.value))
200+
elif isinstance(stmt, ChainedAssignment):
201+
for target in stmt.targets:
202+
if isinstance(target, Identifier):
203+
pairs.append((target.name, stmt.value))
204+
for name, value in pairs:
205+
generator._module_global_names.add(name)
206+
if generator._value_tracks_as_tuple(value):
207+
generator._module_global_tuple_names.add(name)
208+
generator._module_global_list_names.discard(name)
209+
elif generator._value_tracks_as_list(value):
210+
generator._module_global_list_names.add(name)
211+
generator._module_global_tuple_names.discard(name)
212+
else:
213+
generator._module_global_list_names.discard(name)
214+
generator._module_global_tuple_names.discard(name)
215+
216+
217+
def _find_declared_global_names(func_def: FunctionDef) -> set[str]:
218+
"""Return names referenced by ``global ...`` statements inside a function."""
219+
names = set()
220+
221+
def visit(node):
222+
if node is None:
223+
return
224+
if isinstance(node, GlobalStatement):
225+
names.update(node.names)
226+
return
227+
if isinstance(node, list):
228+
for item in node:
229+
visit(item)
230+
return
231+
if isinstance(node, (str, int, float, bool)):
232+
return
233+
for value in getattr(node, "__dict__", {}).values():
234+
visit(value)
235+
236+
visit(getattr(func_def, "body", []))
237+
return names
238+
239+
240+
def _infer_list_like_params(func_def: FunctionDef) -> set[str]:
241+
"""Infer function parameters that should be treated as list-like in WAT."""
242+
params = set(_real_params(func_def))
243+
if not params:
244+
return set()
245+
246+
inferred = set()
247+
248+
def visit(node):
249+
if node is None:
250+
return
251+
if isinstance(node, IndexAccess) and isinstance(node.obj, Identifier):
252+
if node.obj.name in params:
253+
inferred.add(node.obj.name)
254+
elif isinstance(node, CallExpr):
255+
if (
256+
_name(node.func) in _LEN_NAMES
257+
and len(node.args) == 1
258+
and isinstance(node.args[0], Identifier)
259+
and node.args[0].name in params
260+
):
261+
inferred.add(node.args[0].name)
262+
if (
263+
isinstance(node.func, AttributeAccess)
264+
and isinstance(node.func.obj, Identifier)
265+
and node.func.obj.name in params
266+
and node.func.attr in ("append", "extend", "pop")
267+
):
268+
inferred.add(node.func.obj.name)
269+
if isinstance(node, list):
270+
for item in node:
271+
visit(item)
272+
return
273+
if isinstance(node, (str, int, float, bool)):
274+
return
275+
for value in getattr(node, "__dict__", {}).values():
276+
visit(value)
277+
278+
visit(getattr(func_def, "body", []))
279+
return inferred

0 commit comments

Comments
 (0)