Skip to content

Commit 76820ba

Browse files
committed
Check for circular dependency in DIM AT
1 parent 852cb9d commit 76820ba

3 files changed

Lines changed: 99 additions & 24 deletions

File tree

api/optimize.py

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
from typing import NamedTuple
45
from ast_ import NodeVisitor
56
from .config import OPTIONS
67
import api.errmsg
@@ -24,28 +25,8 @@ def __init__(self, obj):
2425
self.obj = obj
2526

2627

27-
class OptimizerVisitor(NodeVisitor):
28-
""" Implements some optimizations
29-
"""
30-
NOP = symbols.NOP() # Return this for "erased" nodes
31-
32-
@staticmethod
33-
def TYPE(type_):
34-
""" Converts a backend type (from api.constants)
35-
to a SymbolTYPE object (taken from the SYMBOL_TABLE).
36-
If type_ is already a SymbolTYPE object, nothing
37-
is done.
38-
"""
39-
if isinstance(type_, symbols.TYPE):
40-
return type_
41-
42-
assert TYPE.is_valid(type_)
43-
return gl.SYMBOL_TABLE.basic_types[type_]
44-
28+
class GenericVisitor(NodeVisitor):
4529
def visit(self, node):
46-
if self.O_LEVEL < 1: # Optimize only if O1 or above
47-
return node
48-
4930
stack = [ToVisit(node)]
5031
last_result = None
5132

@@ -76,6 +57,31 @@ def _visit(self, node):
7657

7758
return meth(node.obj)
7859

60+
61+
class OptimizerVisitor(GenericVisitor):
62+
""" Implements some optimizations
63+
"""
64+
NOP = symbols.NOP() # Return this for "erased" nodes
65+
66+
@staticmethod
67+
def TYPE(type_):
68+
""" Converts a backend type (from api.constants)
69+
to a SymbolTYPE object (taken from the SYMBOL_TABLE).
70+
If type_ is already a SymbolTYPE object, nothing
71+
is done.
72+
"""
73+
if isinstance(type_, symbols.TYPE):
74+
return type_
75+
76+
assert TYPE.is_valid(type_)
77+
return gl.SYMBOL_TABLE.basic_types[type_]
78+
79+
def visit(self, node):
80+
if self.O_LEVEL < 1: # Optimize only if O1 or above
81+
return node
82+
83+
return super().visit(node)
84+
7985
@property
8086
def O_LEVEL(self):
8187
return OPTIONS.optimization.value
@@ -196,7 +202,7 @@ def visit_IF(self, node):
196202
if not block_accessed and chk.is_number(expr_): # constant condition
197203
if expr_.value: # always true (then_)
198204
yield then_
199-
else: # always false (else_)
205+
else: # always false (else_)
200206
yield else_
201207
return
202208

@@ -282,3 +288,70 @@ def _update_bound_status(self, arg: symbols.VARARRAY):
282288

283289
if arg.scope == SCOPE.local and not arg.byref:
284290
arg.scopeRef.owner.locals_size = api.symboltable.SymbolTable.compute_offsets(arg.scopeRef)
291+
292+
293+
class VarDependency(NamedTuple):
294+
parent: symbols.VAR
295+
dependency: symbols.VAR
296+
297+
298+
class VariableVisitor(GenericVisitor):
299+
_original_variable = None
300+
_parent_variable = None
301+
_visited = set()
302+
303+
@staticmethod
304+
def generic_visit(node):
305+
if node not in VariableVisitor._visited:
306+
VariableVisitor._visited.add(node)
307+
for i in range(len(node.children)):
308+
node.children[i] = yield ToVisit(node.children[i])
309+
yield node
310+
311+
def has_circular_dependency(self, var_dependency: VarDependency) -> bool:
312+
if var_dependency.dependency == VariableVisitor._original_variable:
313+
api.errmsg.error(VariableVisitor._original_variable.lineno,
314+
"Circular dependency between '{}' and '{}'".format(
315+
VariableVisitor._original_variable.name, var_dependency.parent))
316+
return True
317+
318+
return False
319+
320+
def get_var_dependencies(self, var_entry: symbols.VAR):
321+
visited = set()
322+
result = set()
323+
324+
def visit_var(entry):
325+
if entry in visited:
326+
return
327+
328+
visited.add(entry)
329+
if not isinstance(entry, symbols.VAR):
330+
for child in entry.children:
331+
visit_var(child)
332+
if isinstance(child, symbols.VAR):
333+
result.add(VarDependency(parent=VariableVisitor._parent_variable, dependency=child))
334+
return
335+
336+
VariableVisitor._parent_variable = entry
337+
if entry.alias is not None:
338+
result.add(VarDependency(parent=entry, dependency=entry.alias))
339+
visit_var(entry.alias)
340+
elif entry.addr is not None:
341+
visit_var(entry.addr)
342+
343+
visit_var(var_entry)
344+
return result
345+
346+
def visit_VARDECL(self, node: symbols.VARDECL):
347+
""" Checks for cyclic dependencies in aliasing variables
348+
"""
349+
VariableVisitor._visited = set()
350+
VariableVisitor._original_variable = node.entry
351+
for dependency in self.get_var_dependencies(node.entry):
352+
if self.has_circular_dependency(dependency):
353+
break
354+
355+
VariableVisitor._visited = set()
356+
VariableVisitor._original_variable = VariableVisitor._parent_variable = None
357+
yield node

libzxbc/zxb.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ def main(args=None, emitter=None):
331331
backend.MEMORY[:] = []
332332

333333
# This will fill MEMORY with global declared variables
334+
var_checker = api.optimize.VariableVisitor()
335+
var_checker.visit(zxbparser.data_ast)
334336
translator = arch.zx48k.VarTranslator()
335337
translator.visit(zxbparser.data_ast)
336338
if gl.has_errors:

libzxbc/zxbparser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ def p_var_decl_at(p):
669669
if entry is None:
670670
return
671671

672-
if p[5].token == 'CONST':
672+
if p[5].token in 'CONST':
673673
tmp = p[5].expr
674674
if tmp.token == 'UNARY' and tmp.operator == 'ADDRESS': # Must be an ID
675675
if tmp.operand.token in ('VAR', 'LABEL'):
@@ -691,7 +691,7 @@ def p_var_decl_at(p):
691691
api.errmsg.syntax_error_address_must_be_constant(p.lineno(4))
692692
return
693693
else:
694-
entry.addr = str(make_typecast(_TYPE(gl.PTR_TYPE), p[5], p.lineno(4)).value)
694+
entry.addr = make_typecast(_TYPE(gl.PTR_TYPE), p[5], p.lineno(4))
695695
entry.accessed = True
696696
if entry.scope == SCOPE.local:
697697
SYMBOL_TABLE.make_static(entry.name)

0 commit comments

Comments
 (0)