First working solution of new task
This commit is contained in:
BIN
Project-02-03-04-05/Source.gv.png
Normal file
BIN
Project-02-03-04-05/Source.gv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 374 KiB |
9
Project-02-03-04-05/cfa/__init__.py
Normal file
9
Project-02-03-04-05/cfa/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from .live_variables import LiveVariablesAnalysis, Var
|
||||||
|
from .reached_uses import ReachedUsesAnalysis, UseFact
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Var",
|
||||||
|
"UseFact",
|
||||||
|
"LiveVariablesAnalysis",
|
||||||
|
"ReachedUsesAnalysis",
|
||||||
|
]
|
||||||
192
Project-02-03-04-05/cfa/analysis_dot.py
Normal file
192
Project-02-03-04-05/cfa/analysis_dot.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import syntax
|
||||||
|
import colorsys
|
||||||
|
from cfg.CFG_Node import CFG_DIAMOND
|
||||||
|
|
||||||
|
|
||||||
|
def _expr_used_names(expr) -> set[str]:
|
||||||
|
"""Collect variable names (syntax.ID) used inside an expression subtree."""
|
||||||
|
used: set[str] = set()
|
||||||
|
|
||||||
|
def visit(node):
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
if isinstance(node, syntax.ID):
|
||||||
|
used.add(node.name)
|
||||||
|
return
|
||||||
|
if isinstance(node, syntax.EXPRESSION):
|
||||||
|
for _, child in node.children():
|
||||||
|
visit(child)
|
||||||
|
|
||||||
|
visit(expr)
|
||||||
|
return used
|
||||||
|
|
||||||
|
|
||||||
|
def _show_analysis_on_node(node) -> bool:
|
||||||
|
"""Return True if analysis annotations should be displayed for this node."""
|
||||||
|
ast = node.ast_node
|
||||||
|
if isinstance(node, CFG_DIAMOND):
|
||||||
|
return False
|
||||||
|
if ast is None:
|
||||||
|
return False
|
||||||
|
return isinstance(
|
||||||
|
ast,
|
||||||
|
(
|
||||||
|
syntax.ASSIGN,
|
||||||
|
syntax.CALL,
|
||||||
|
syntax.IF,
|
||||||
|
syntax.WHILE,
|
||||||
|
syntax.DECL,
|
||||||
|
syntax.LET,
|
||||||
|
syntax.SEQ,
|
||||||
|
syntax.COMP,
|
||||||
|
syntax.EQOP,
|
||||||
|
syntax.LOP,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lv_in_for_display(node, analysis):
|
||||||
|
"""Display-level IN set for LV."""
|
||||||
|
in_set = set(analysis.in_sets.get(node.id, set()))
|
||||||
|
ast_node = node.ast_node
|
||||||
|
if isinstance(ast_node, syntax.ASSIGN):
|
||||||
|
func = analysis._func_scope.get(node.id)
|
||||||
|
rhs_vars = {
|
||||||
|
analysis._resolve_var(func, name)
|
||||||
|
for name in _expr_used_names(ast_node.expr)
|
||||||
|
}
|
||||||
|
in_set |= rhs_vars
|
||||||
|
return in_set
|
||||||
|
|
||||||
|
|
||||||
|
def _node_color(node_id: int) -> tuple[str, str]:
|
||||||
|
"""Return (edge_color, fill_color) deterministically for a node id."""
|
||||||
|
# Golden-angle hue distribution gives stable, distinct colors.
|
||||||
|
hue = ((node_id * 0.6180339887498949) % 1.0)
|
||||||
|
edge_rgb = colorsys.hsv_to_rgb(hue, 0.70, 0.82)
|
||||||
|
fill_rgb = colorsys.hsv_to_rgb(hue, 0.28, 0.97)
|
||||||
|
|
||||||
|
def to_hex(rgb):
|
||||||
|
r, g, b = (int(round(c * 255)) for c in rgb)
|
||||||
|
return f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
|
||||||
|
return to_hex(edge_rgb), to_hex(fill_rgb)
|
||||||
|
|
||||||
|
|
||||||
|
def run_all_analyses(cfg):
|
||||||
|
"""Run Live Variables and Reached Uses on *cfg*.
|
||||||
|
|
||||||
|
Returns ``(analyses, annotations, ru_edges)`` where:
|
||||||
|
• *analyses* is a dict with keys ``"lv"`` and ``"ru"``,
|
||||||
|
• *annotations* contains only LivingVariables helper-node labels,
|
||||||
|
• *ru_edges* maps definition-node ids to reached use-node ids.
|
||||||
|
"""
|
||||||
|
node_by_id = {n.id: n for n in cfg.nodes()}
|
||||||
|
|
||||||
|
from cfa.live_variables import LiveVariablesAnalysis
|
||||||
|
from cfa.reached_uses import ReachedUsesAnalysis
|
||||||
|
|
||||||
|
lv = LiveVariablesAnalysis(cfg)
|
||||||
|
ru = ReachedUsesAnalysis(cfg)
|
||||||
|
|
||||||
|
all_ids = set(lv.in_sets.keys()) | set(lv.out_sets.keys())
|
||||||
|
annotations = {
|
||||||
|
nid: (
|
||||||
|
"LivingVariables\\n"
|
||||||
|
f"In := {sorted(_lv_in_for_display(node_by_id[nid], lv))}\\n"
|
||||||
|
f"Out := {sorted(lv.out_sets.get(nid, set()))}"
|
||||||
|
)
|
||||||
|
for nid in all_ids
|
||||||
|
if lv.in_sets.get(nid, set()) or lv.out_sets.get(nid, set())
|
||||||
|
if nid in node_by_id and _show_analysis_on_node(node_by_id[nid])
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"lv": lv, "ru": ru}, annotations, ru.reached_uses_by_node()
|
||||||
|
|
||||||
|
|
||||||
|
def analysis_to_dot(
|
||||||
|
cfg,
|
||||||
|
annotations: dict[int, str],
|
||||||
|
analysis_name: str,
|
||||||
|
ru_edges: dict[int, list[int]] | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Return a DOT string for *cfg* annotated with analysis results."""
|
||||||
|
lines = [
|
||||||
|
"digraph CFG {",
|
||||||
|
f' // Analysis: {analysis_name}',
|
||||||
|
' graph [splines=ortho, overlap=false, ranksep=0.7, nodesep=0.45];',
|
||||||
|
' node [fontname="Helvetica"];',
|
||||||
|
]
|
||||||
|
color_nodes = set(annotations.keys()) | set((ru_edges or {}).keys())
|
||||||
|
node_colors = {nid: _node_color(nid) for nid in color_nodes}
|
||||||
|
|
||||||
|
def emit(node):
|
||||||
|
base_label = node.dot_label() or ""
|
||||||
|
shape = node.dot_shape
|
||||||
|
style = node.dot_style
|
||||||
|
style_str = f", {style}" if style else ""
|
||||||
|
lines.append(f' n{node.id} [label="{base_label}", shape={shape}{style_str}];')
|
||||||
|
|
||||||
|
if node.id in annotations:
|
||||||
|
ann_id = f"a{node.id}"
|
||||||
|
ann_label = annotations[node.id].replace('"', '\\"')
|
||||||
|
edge_color, fill_color = node_colors.get(node.id, ("#1f77b4", "#d9ecff"))
|
||||||
|
lines.append(
|
||||||
|
f' {ann_id} [label="{ann_label}", shape=note, '
|
||||||
|
f'style="filled", fillcolor="{fill_color}", color="{edge_color}", '
|
||||||
|
f'fontcolor="{edge_color}"];'
|
||||||
|
)
|
||||||
|
lines.append(
|
||||||
|
f' {ann_id} -> n{node.id} [style=dotted, arrowhead=none, '
|
||||||
|
f'color="{edge_color}"];'
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, child in enumerate(sorted(node.children, key=lambda n: n.id)):
|
||||||
|
edge_label = ""
|
||||||
|
if isinstance(node, CFG_DIAMOND):
|
||||||
|
if i == 0:
|
||||||
|
edge_label = ' [label="T"]'
|
||||||
|
elif i == 1:
|
||||||
|
edge_label = ' [label="F"]'
|
||||||
|
lines.append(f" n{node.id} -> n{child.id}{edge_label};")
|
||||||
|
|
||||||
|
cfg.traverse(emit, start=cfg.START)
|
||||||
|
|
||||||
|
if ru_edges:
|
||||||
|
for idx, def_id in enumerate(sorted(ru_edges)):
|
||||||
|
use_ids = sorted(set(ru_edges[def_id]))
|
||||||
|
if not use_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# One routing hub per definition node to mimic UML-like
|
||||||
|
# "out to the side, then down/across to targets" connectors.
|
||||||
|
side = "e" if idx % 2 == 0 else "w"
|
||||||
|
source_port = "se" if side == "e" else "sw"
|
||||||
|
hub_id = f"rh{def_id}"
|
||||||
|
edge_color, fill_color = node_colors.get(def_id, ("#1f77b4", "#d9ecff"))
|
||||||
|
|
||||||
|
lines.append(
|
||||||
|
f' {hub_id} [shape=point, width=0.05, height=0.05, '
|
||||||
|
f'color="{edge_color}", fillcolor="{edge_color}", style=filled];'
|
||||||
|
)
|
||||||
|
lines.append(f" {{ rank=same; n{def_id}; {hub_id}; }}")
|
||||||
|
lines.append(
|
||||||
|
f' n{def_id}:{source_port} -> {hub_id} [color="{edge_color}", '
|
||||||
|
f'style=dashed, penwidth=1.2, arrowhead=none, constraint=false, '
|
||||||
|
f'tailclip=true, headclip=true];'
|
||||||
|
)
|
||||||
|
|
||||||
|
for use_id in use_ids:
|
||||||
|
if side == "e":
|
||||||
|
target_port = "ne" if (use_id % 2 == 0) else "se"
|
||||||
|
else:
|
||||||
|
target_port = "nw" if (use_id % 2 == 0) else "sw"
|
||||||
|
lines.append(
|
||||||
|
f' {hub_id} -> n{use_id}:{target_port} [color="{edge_color}", '
|
||||||
|
f'fontcolor="{edge_color}", fontsize=8, style=dashed, '
|
||||||
|
f'penwidth=1.0, arrowsize=0.6, constraint=false, '
|
||||||
|
f'tailclip=true, headclip=true];'
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.append("}")
|
||||||
|
return "\n".join(lines)
|
||||||
351
Project-02-03-04-05/cfa/live_variables.py
Normal file
351
Project-02-03-04-05/cfa/live_variables.py
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
"""
|
||||||
|
live_variables.py — Live Variables backward dataflow analysis for TRIPLA CFGs.
|
||||||
|
|
||||||
|
A variable v is *live* at the entry of node n if there exists a path
|
||||||
|
n → … → use(v) where v is not redefined along the way.
|
||||||
|
|
||||||
|
Data structures
|
||||||
|
---------------
|
||||||
|
gen dict[int, set[Var]] — GEN(n) = variables *used* at n
|
||||||
|
kill dict[int, set[Var]] — KILL(n) = variables *defined* at n
|
||||||
|
in_sets dict[int, set[Var]] — live variables at node *entry*
|
||||||
|
out_sets dict[int, set[Var]] — live variables at node *exit*
|
||||||
|
|
||||||
|
Transfer equations (backward):
|
||||||
|
OUT(n) = ∪ IN(s) for all successors s
|
||||||
|
IN(n) = (OUT(n) − KILL(n)) ∪ GEN(n)
|
||||||
|
|
||||||
|
Variables are represented in scoped form ``(scope, name)``, e.g. ``("f","x")``.
|
||||||
|
This avoids collisions between equal variable names in different functions.
|
||||||
|
|
||||||
|
This module also exports ``_BackwardAnalysisBase``, the shared base class
|
||||||
|
that ``ReachedUsesAnalysis`` in reached_uses.py inherits from. The base
|
||||||
|
provides:
|
||||||
|
• AST traversal to collect function-nesting and parameter metadata
|
||||||
|
• Lexical variable resolution (parameter shadowing handled correctly)
|
||||||
|
• BFS-based CFG-node → owning-function assignment
|
||||||
|
• Unified uses / defs extraction for all node types
|
||||||
|
|
||||||
|
Var = tuple[str, str]
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import cfg_build
|
||||||
|
import syntax
|
||||||
|
from cfg.CFG_Node import CFG_START
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cfg.CFG import CFG
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Public type alias (imported by reached_uses.py)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GLOBAL_SCOPE = ""
|
||||||
|
Var = tuple[str, str] # (function_name|GLOBAL_SCOPE, variable_name)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Shared base: function metadata, scope assignment, uses/defs extraction
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class _BackwardAnalysisBase:
|
||||||
|
"""Infrastructure shared by LiveVariablesAnalysis and ReachedUsesAnalysis.
|
||||||
|
|
||||||
|
Calling ``super().__init__(cfg)`` from a subclass:
|
||||||
|
1. Snapshots cfg_build.FUNCTIONS.
|
||||||
|
2. Collects AST-level function-nesting and parameter metadata.
|
||||||
|
3. BFS-assigns every CFG node to its owning function.
|
||||||
|
4. Extracts uses and defs for every CFG node.
|
||||||
|
|
||||||
|
After __init__ the following attributes are available to subclasses:
|
||||||
|
|
||||||
|
self.cfg — the CFG object
|
||||||
|
self._functions — dict[str, tuple]: snapshot of cfg_build.FUNCTIONS
|
||||||
|
self._func_parent — dict[str, str|None]: lexical parent per function
|
||||||
|
self._func_params — dict[str, tuple[str,...]]: params per function
|
||||||
|
self._func_scope — dict[int, str]: node-id → owning function name
|
||||||
|
self.uses — dict[int, set[Var]]: variables used at each node
|
||||||
|
self.defs — dict[int, set[Var]]: variables defined at each node
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cfg: "CFG") -> None:
|
||||||
|
self.cfg = cfg
|
||||||
|
# Snapshot FUNCTIONS so later global-state resets do not affect us.
|
||||||
|
self._functions: dict[str, tuple] = dict(cfg_build.FUNCTIONS)
|
||||||
|
|
||||||
|
self.uses: dict[int, set[Var]] = {}
|
||||||
|
self.defs: dict[int, set[Var]] = {}
|
||||||
|
|
||||||
|
self._func_parent, self._func_params = self._collect_function_metadata()
|
||||||
|
self._func_scope: dict[int, str] = self._compute_function_scope()
|
||||||
|
self._extract_uses_defs()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1a — Walk AST to collect lexical nesting + parameter lists
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _collect_function_metadata(
|
||||||
|
self,
|
||||||
|
) -> tuple[dict[str, str | None], dict[str, tuple[str, ...]]]:
|
||||||
|
"""Walk the AST and collect function-parent and parameter information.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
func_parent : dict[str, str | None]
|
||||||
|
func_parent[f] is the name of the immediately enclosing function
|
||||||
|
(or None for top-level functions).
|
||||||
|
func_params : dict[str, tuple[str, ...]]
|
||||||
|
func_params[f] is the ordered tuple of formal parameter names of f.
|
||||||
|
"""
|
||||||
|
func_parent: dict[str, str | None] = {}
|
||||||
|
func_params: dict[str, tuple[str, ...]] = {}
|
||||||
|
|
||||||
|
def visit(expr: syntax.EXPRESSION | None, current_func: str | None) -> None:
|
||||||
|
if expr is None:
|
||||||
|
return
|
||||||
|
if isinstance(expr, syntax.LET):
|
||||||
|
decls = expr.decl if isinstance(expr.decl, list) else [expr.decl]
|
||||||
|
# Register metadata for each declared function.
|
||||||
|
for d in decls:
|
||||||
|
if isinstance(d, syntax.DECL):
|
||||||
|
# Use assignment (last-seen wins) to stay consistent
|
||||||
|
# with cfg_build.FUNCTIONS, which also overwrites on
|
||||||
|
# duplicate names. setdefault (first-seen wins) would
|
||||||
|
# disagree when a nested function shadows a top-level
|
||||||
|
# one with the same name, causing wrong scope resolution.
|
||||||
|
func_parent[d.f_name] = current_func
|
||||||
|
func_params[d.f_name] = tuple(d.params)
|
||||||
|
# Recurse into function bodies and the in-expression.
|
||||||
|
for d in decls:
|
||||||
|
if isinstance(d, syntax.DECL):
|
||||||
|
visit(d.body, d.f_name)
|
||||||
|
else:
|
||||||
|
visit(d, current_func)
|
||||||
|
visit(expr.body, current_func)
|
||||||
|
return
|
||||||
|
for _, child in expr.children():
|
||||||
|
visit(child, current_func)
|
||||||
|
|
||||||
|
visit(self.cfg.ast, None)
|
||||||
|
return func_parent, func_params
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1b — Resolve a variable name through the lexical scope chain
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _resolve_var(self, func: str | None, name: str) -> Var:
|
||||||
|
"""Resolve a variable name via lexical scope chain."""
|
||||||
|
if func is None:
|
||||||
|
return (GLOBAL_SCOPE, name)
|
||||||
|
|
||||||
|
cur: str | None = func
|
||||||
|
seen: set[str] = set()
|
||||||
|
while cur is not None and cur not in seen:
|
||||||
|
seen.add(cur)
|
||||||
|
if name in self._func_params.get(cur, ()):
|
||||||
|
return (cur, name)
|
||||||
|
cur = self._func_parent.get(cur)
|
||||||
|
|
||||||
|
# Fallback: local variable in current function scope.
|
||||||
|
return (func, name)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 2 — BFS-assign every CFG node to its owning function
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _compute_function_scope(self) -> dict[int, str]:
|
||||||
|
"""BFS from each function's START node; return node-id → function-name.
|
||||||
|
|
||||||
|
Two stopping conditions keep attribution strictly inside each function:
|
||||||
|
|
||||||
|
1. Do not follow into a *different* function's CFG_START (prevents
|
||||||
|
attributing callee body nodes to the caller, and vice-versa).
|
||||||
|
2. Do not follow *past* the function's own CFG_END (prevents
|
||||||
|
following CFG_END → CFG_RETURN → continuation nodes that belong
|
||||||
|
to the *caller* context, which caused variables used there to be
|
||||||
|
resolved in the wrong scope).
|
||||||
|
|
||||||
|
The first function whose BFS claims a node wins.
|
||||||
|
"""
|
||||||
|
functions = self._functions
|
||||||
|
func_scope: dict[int, str] = {}
|
||||||
|
all_f_start_ids: set[int] = {fs.id for _, (fs, _) in functions.items()}
|
||||||
|
|
||||||
|
for f_name, (f_start, f_end) in functions.items():
|
||||||
|
queue: deque = deque([f_start])
|
||||||
|
while queue:
|
||||||
|
node = queue.popleft()
|
||||||
|
if node.id in func_scope:
|
||||||
|
continue # already claimed by an earlier function
|
||||||
|
func_scope[node.id] = f_name
|
||||||
|
# Stop here — do not follow CFG_END into caller context.
|
||||||
|
if node.id == f_end.id:
|
||||||
|
continue
|
||||||
|
for child in node.children:
|
||||||
|
# Do not follow into a different function's START.
|
||||||
|
if (
|
||||||
|
isinstance(child, CFG_START)
|
||||||
|
and child.id in all_f_start_ids
|
||||||
|
and child.id != f_start.id
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
queue.append(child)
|
||||||
|
|
||||||
|
return func_scope
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 3 — Extract uses / defs for every CFG node
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _extract_uses_defs(self) -> None:
|
||||||
|
"""Populate ``self.uses`` and ``self.defs`` for every node in the CFG.
|
||||||
|
|
||||||
|
Extraction rules:
|
||||||
|
• CFG_START(DECL f(p1,…,pk)) → defs = {(f,p1), …, (f,pk)}
|
||||||
|
• Node wrapping ID(x) → uses = {lexical_resolve(func, x)}
|
||||||
|
• Node wrapping ASSIGN(x = e) → defs = {lexical_resolve(func, x)}
|
||||||
|
• Everything else → uses = {}, defs = {}
|
||||||
|
|
||||||
|
Sub-expressions already have their own CFG nodes and are not
|
||||||
|
re-inspected here; each node is responsible only for its own ast_node.
|
||||||
|
"""
|
||||||
|
for node in self.cfg.nodes():
|
||||||
|
nid = node.id
|
||||||
|
func = self._func_scope.get(nid) # None → outer / global scope
|
||||||
|
ast = node.ast_node
|
||||||
|
|
||||||
|
uses: set[Var] = set()
|
||||||
|
defs: set[Var] = set()
|
||||||
|
|
||||||
|
if isinstance(node, CFG_START) and isinstance(ast, syntax.DECL):
|
||||||
|
# Function entry defines each formal parameter.
|
||||||
|
for param in ast.params:
|
||||||
|
defs.add((ast.f_name, param))
|
||||||
|
elif ast is not None:
|
||||||
|
if isinstance(ast, syntax.ID):
|
||||||
|
resolved = self._resolve_var(func, ast.name)
|
||||||
|
uses.add(resolved)
|
||||||
|
elif isinstance(ast, syntax.ASSIGN):
|
||||||
|
resolved = self._resolve_var(func, ast.var.name)
|
||||||
|
defs.add(resolved)
|
||||||
|
|
||||||
|
self.uses[nid] = uses
|
||||||
|
self.defs[nid] = defs
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Live Variables Analysis
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class LiveVariablesAnalysis(_BackwardAnalysisBase):
|
||||||
|
"""Backward dataflow analysis: Live Variables.
|
||||||
|
|
||||||
|
A variable (f, x) is *live* at the entry of node n if there is a path
|
||||||
|
from n to some use of (f, x) along which (f, x) is not redefined.
|
||||||
|
|
||||||
|
This is the simpler predecessor to ReachedUsesAnalysis (reached_uses.py):
|
||||||
|
it tracks which variables are live, not *where* they are used.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
gen dict[int, set[Var]] GEN(n) = uses(n) — vars used at n
|
||||||
|
kill dict[int, set[Var]] KILL(n) = defs(n) — vars defined at n
|
||||||
|
in_sets dict[int, set[Var]] live variables at n's *entry*
|
||||||
|
out_sets dict[int, set[Var]] live variables at n's *exit*
|
||||||
|
|
||||||
|
(uses and defs are identical to gen / kill and are inherited from the
|
||||||
|
base class.)
|
||||||
|
|
||||||
|
Transfer equations (backward):
|
||||||
|
OUT(n) = ∪ IN(s) for all successors s
|
||||||
|
IN(n) = (OUT(n) − KILL(n)) ∪ GEN(n)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cfg: "CFG") -> None:
|
||||||
|
# Base populates uses, defs, _func_scope, etc.
|
||||||
|
super().__init__(cfg)
|
||||||
|
|
||||||
|
self.gen: dict[int, set[Var]] = {}
|
||||||
|
self.kill: dict[int, set[Var]] = {}
|
||||||
|
self.in_sets: dict[int, set[Var]] = {}
|
||||||
|
self.out_sets: dict[int, set[Var]] = {}
|
||||||
|
|
||||||
|
self._build_gen_kill()
|
||||||
|
self.solve()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Build gen / kill; initialise in / out to ∅
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _build_gen_kill(self) -> None:
|
||||||
|
"""GEN(n) = uses(n), KILL(n) = defs(n); initialise in/out sets."""
|
||||||
|
for node in self.cfg.nodes():
|
||||||
|
nid = node.id
|
||||||
|
self.gen[nid] = set(self.uses[nid])
|
||||||
|
self.kill[nid] = set(self.defs[nid])
|
||||||
|
self.in_sets[nid] = set()
|
||||||
|
self.out_sets[nid] = set()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Backward worklist fixpoint
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def solve(self) -> None:
|
||||||
|
"""Backward worklist until fixpoint.
|
||||||
|
|
||||||
|
Transfer:
|
||||||
|
OUT(n) = ∪ IN(s) for all successors s
|
||||||
|
IN(n) = (OUT(n) − KILL(n)) ∪ GEN(n)
|
||||||
|
|
||||||
|
Only nodes reachable from cfg.START are processed (guard against
|
||||||
|
propagate=False parent references from CFG.__remove_and_rewire).
|
||||||
|
"""
|
||||||
|
nodes = list(self.cfg.nodes())
|
||||||
|
known: set[int] = set(self.gen.keys())
|
||||||
|
id_to_node = {n.id: n for n in nodes}
|
||||||
|
worklist: deque = deque(nodes)
|
||||||
|
|
||||||
|
# Build predecessor relation from children edges. This is more reliable
|
||||||
|
# than node.parents because CFG rewiring may add edges with
|
||||||
|
# propagate=False, leaving parent links stale.
|
||||||
|
preds: dict[int, set[int]] = {nid: set() for nid in known}
|
||||||
|
for node in nodes:
|
||||||
|
for child in node.children:
|
||||||
|
if child.id in known:
|
||||||
|
preds[child.id].add(node.id)
|
||||||
|
|
||||||
|
while worklist:
|
||||||
|
node = worklist.popleft()
|
||||||
|
nid = node.id
|
||||||
|
|
||||||
|
new_out: set[Var] = set()
|
||||||
|
for child in node.children:
|
||||||
|
if child.id in known:
|
||||||
|
new_out |= self.in_sets[child.id]
|
||||||
|
|
||||||
|
new_in: set[Var] = (new_out - self.kill[nid]) | self.gen[nid]
|
||||||
|
|
||||||
|
if new_out != self.out_sets[nid] or new_in != self.in_sets[nid]:
|
||||||
|
self.out_sets[nid] = new_out
|
||||||
|
self.in_sets[nid] = new_in
|
||||||
|
for pred_id in preds[nid]:
|
||||||
|
worklist.append(id_to_node[pred_id])
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Result
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def live_vars_by_node(self) -> dict[int, set[Var]]:
|
||||||
|
"""Return the live-variable set at the *entry* of each node.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict[int, set[Var]]
|
||||||
|
Keys: CFG node ids whose in_set is non-empty.
|
||||||
|
Values: copy of the live-variable set at that node's entry.
|
||||||
|
"""
|
||||||
|
return {nid: set(vs) for nid, vs in self.in_sets.items() if vs}
|
||||||
203
Project-02-03-04-05/cfa/reached_uses.py
Normal file
203
Project-02-03-04-05/cfa/reached_uses.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
"""
|
||||||
|
reached_uses.py — Reached-Uses backward dataflow analysis for TRIPLA CFGs.
|
||||||
|
|
||||||
|
Extends ``_BackwardAnalysisBase`` from live_variables.py, which provides the
|
||||||
|
shared function-scope resolution and uses/defs extraction machinery. The Live
|
||||||
|
Variables analysis (LiveVariablesAnalysis) in that module is the simpler
|
||||||
|
predecessor of this analysis (tip from the course notes: implement LV first,
|
||||||
|
then extend to RU).
|
||||||
|
|
||||||
|
How ReachedUsesAnalysis extends LiveVariablesAnalysis
|
||||||
|
------------------------------------------------------
|
||||||
|
Live Variables tracks *which* variables are live at each node (set[Var]).
|
||||||
|
Reached Uses additionally tracks *where* each variable is used by attaching
|
||||||
|
the use-node id to every fact, giving set[UseFact] = set[tuple[int, Var]].
|
||||||
|
|
||||||
|
The transfer function changes accordingly:
|
||||||
|
LV: IN(n) = (OUT(n) − KILL_LV(n)) ∪ GEN_LV(n) [sets of Var]
|
||||||
|
RU: IN(n) = (OUT(n) − KILL_RU(n)) ∪ GEN_RU(n) [sets of UseFact]
|
||||||
|
|
||||||
|
GEN_LV(n) = uses(n) — set[Var]
|
||||||
|
GEN_RU(n) = { (n.id, var) | var ∈ uses(n) } — set[UseFact]
|
||||||
|
|
||||||
|
KILL_LV(n) = defs(n) — set[Var]
|
||||||
|
KILL_RU(n) = { (uid, var) | var ∈ defs(n), — set[UseFact]
|
||||||
|
(uid, var) ∈ all_uses_by_var[var] }
|
||||||
|
|
||||||
|
The set-difference in both cases removes exactly the facts for variables
|
||||||
|
that are defined at n — equivalent to the ⊖ operator from the lecture
|
||||||
|
slides (M ⊖ K = {(p,id) ∈ M | id ∉ K}).
|
||||||
|
|
||||||
|
Type aliases
|
||||||
|
------------
|
||||||
|
Var = tuple[str, str] # (scope, variable_name)
|
||||||
|
UseFact = tuple[int, Var] # (use_node_id, scoped_var)
|
||||||
|
|
||||||
|
Analysis attributes (all populated after construction)
|
||||||
|
------------------------------------------------------
|
||||||
|
uses dict[int, set[Var]]
|
||||||
|
defs dict[int, set[Var]]
|
||||||
|
gen dict[int, set[UseFact]]
|
||||||
|
kill dict[int, set[UseFact]]
|
||||||
|
in_sets dict[int, set[UseFact]]
|
||||||
|
out_sets dict[int, set[UseFact]]
|
||||||
|
all_uses_by_var dict[Var, set[UseFact]]
|
||||||
|
|
||||||
|
Final result
|
||||||
|
------------
|
||||||
|
reached_uses_by_node() → dict[int, list[int]]
|
||||||
|
Keys: defining-node ids
|
||||||
|
Values: sorted, deduplicated list of use-node ids reached by the def
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
# Import the shared base class (and Var) from the Live Variables module.
|
||||||
|
from cfa.live_variables import _BackwardAnalysisBase, Var
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cfg.CFG import CFG
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Public type aliases (re-exported so tests/reached_uses_stub.py can pick up
|
||||||
|
# ReachedUsesAnalysis without needing to know about live_variables.py)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UseFact = tuple[int, Var] # (use_node_id, scoped_var)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Reached-Uses Analysis
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class ReachedUsesAnalysis(_BackwardAnalysisBase):
|
||||||
|
"""Backward dataflow analysis: Reached Uses.
|
||||||
|
|
||||||
|
Inherits uses/defs extraction and function-scope resolution from
|
||||||
|
_BackwardAnalysisBase (live_variables.py). Extends it with use-fact
|
||||||
|
tracking: each fact carries the id of the node where the variable is used,
|
||||||
|
enabling def-use pairs to be recovered from the fixpoint solution.
|
||||||
|
|
||||||
|
Transfer equations (backward):
|
||||||
|
OUT(n) = ∪ IN(s) for all successors s
|
||||||
|
IN(n) = GEN(n) ∪ (OUT(n) − KILL(n))
|
||||||
|
|
||||||
|
GEN(n) = { (n.id, var) | var ∈ uses(n) }
|
||||||
|
KILL(n) = { (uid, var) | var ∈ defs(n),
|
||||||
|
(uid, var) ∈ all_uses_by_var[var] }
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cfg: "CFG") -> None:
|
||||||
|
# Base populates: uses, defs, _func_scope, _func_parent, _func_params.
|
||||||
|
super().__init__(cfg)
|
||||||
|
|
||||||
|
self.gen: dict[int, set[UseFact]] = {}
|
||||||
|
self.kill: dict[int, set[UseFact]] = {}
|
||||||
|
self.in_sets: dict[int, set[UseFact]] = {}
|
||||||
|
self.out_sets: dict[int, set[UseFact]] = {}
|
||||||
|
self.all_uses_by_var: dict[Var, set[UseFact]] = {}
|
||||||
|
|
||||||
|
self._build_gen_kill()
|
||||||
|
self.solve()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 1 — Build gen, kill, all_uses_by_var; initialise in/out
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _build_gen_kill(self) -> None:
|
||||||
|
"""Compute gen and kill sets; populate all_uses_by_var."""
|
||||||
|
# GEN[n] = { (n.id, var) | var ∈ uses[n] }
|
||||||
|
for node in self.cfg.nodes():
|
||||||
|
nid = node.id
|
||||||
|
self.gen[nid] = {(nid, var) for var in self.uses[nid]}
|
||||||
|
self.in_sets[nid] = set()
|
||||||
|
self.out_sets[nid] = set()
|
||||||
|
|
||||||
|
# all_uses_by_var: index all use-facts by their variable.
|
||||||
|
for nid, facts in self.gen.items():
|
||||||
|
for (uid, var) in facts:
|
||||||
|
self.all_uses_by_var.setdefault(var, set()).add((uid, var))
|
||||||
|
|
||||||
|
# KILL[n] = all use-facts for variables defined at n.
|
||||||
|
for node in self.cfg.nodes():
|
||||||
|
nid = node.id
|
||||||
|
kill_n: set[UseFact] = set()
|
||||||
|
for var in self.defs[nid]:
|
||||||
|
if var in self.all_uses_by_var:
|
||||||
|
kill_n |= self.all_uses_by_var[var]
|
||||||
|
self.kill[nid] = kill_n
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Step 2 — Backward worklist fixpoint
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def solve(self) -> None:
|
||||||
|
"""Backward worklist until fixpoint.
|
||||||
|
|
||||||
|
Transfer:
|
||||||
|
OUT(n) = ∪ IN(s) for all successors s
|
||||||
|
IN(n) = GEN(n) ∪ (OUT(n) − KILL(n))
|
||||||
|
|
||||||
|
Only nodes reachable from cfg.START are processed (guard against
|
||||||
|
propagate=False parent references from CFG.__remove_and_rewire).
|
||||||
|
"""
|
||||||
|
nodes = list(self.cfg.nodes())
|
||||||
|
known: set[int] = set(self.gen.keys()) # ids of cfg.nodes()
|
||||||
|
id_to_node = {n.id: n for n in nodes}
|
||||||
|
worklist: deque = deque(nodes)
|
||||||
|
|
||||||
|
# Build predecessor relation from children edges. CFG rewiring may
|
||||||
|
# create edges with propagate=False, so node.parents can be stale.
|
||||||
|
preds: dict[int, set[int]] = {nid: set() for nid in known}
|
||||||
|
for node in nodes:
|
||||||
|
for child in node.children:
|
||||||
|
if child.id in known:
|
||||||
|
preds[child.id].add(node.id)
|
||||||
|
|
||||||
|
while worklist:
|
||||||
|
node = worklist.popleft()
|
||||||
|
nid = node.id
|
||||||
|
|
||||||
|
new_out: set[UseFact] = set()
|
||||||
|
for child in node.children:
|
||||||
|
if child.id in known:
|
||||||
|
new_out |= self.in_sets[child.id]
|
||||||
|
|
||||||
|
new_in: set[UseFact] = self.gen[nid] | (new_out - self.kill[nid])
|
||||||
|
|
||||||
|
if new_out != self.out_sets[nid] or new_in != self.in_sets[nid]:
|
||||||
|
self.out_sets[nid] = new_out
|
||||||
|
self.in_sets[nid] = new_in
|
||||||
|
for pred_id in preds[nid]:
|
||||||
|
worklist.append(id_to_node[pred_id])
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Public result
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def reached_uses_by_node(self) -> dict[int, list[int]]:
|
||||||
|
"""Return the final reached-uses result.
|
||||||
|
|
||||||
|
For each defining node d:
|
||||||
|
result[d.id] = sorted list of use-node ids u such that
|
||||||
|
(u, var) ∈ OUT[d] for some var ∈ defs[d].
|
||||||
|
|
||||||
|
Semantics: the definition at d of variable var reaches the use at u
|
||||||
|
if there is a CFG path d → … → u along which var is not redefined.
|
||||||
|
|
||||||
|
Only nodes with at least one definition appear as keys.
|
||||||
|
"""
|
||||||
|
result: dict[int, list[int]] = {}
|
||||||
|
for node in self.cfg.nodes():
|
||||||
|
nid = node.id
|
||||||
|
defs_n = self.defs[nid]
|
||||||
|
if not defs_n:
|
||||||
|
continue
|
||||||
|
reached: set[int] = set()
|
||||||
|
for (uid, var) in self.out_sets[nid]:
|
||||||
|
if var in defs_n:
|
||||||
|
reached.add(uid)
|
||||||
|
result[nid] = sorted(reached)
|
||||||
|
return result
|
||||||
183
Project-02-03-04-05/cfadots/dataflow_analysis.dot
Normal file
183
Project-02-03-04-05/cfadots/dataflow_analysis.dot
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
digraph CFG {
|
||||||
|
// Analysis: Live Variables + Reached Uses
|
||||||
|
graph [splines=ortho, overlap=false, ranksep=0.7, nodesep=0.45];
|
||||||
|
node [fontname="Helvetica"];
|
||||||
|
n1 [label="START", shape=ellipse, style=filled, color=gray];
|
||||||
|
n1 -> n3;
|
||||||
|
n3 [label="33", shape=box];
|
||||||
|
n3 -> n4;
|
||||||
|
n4 [label="s = 33", shape=box];
|
||||||
|
a4 [label="LivingVariables\nIn := []\nOut := [('', 's')]", shape=note, style="filled", fillcolor="#b2f7ec", color="#3fd1b9", fontcolor="#3fd1b9"];
|
||||||
|
a4 -> n4 [style=dotted, arrowhead=none, color="#3fd1b9"];
|
||||||
|
n4 -> n5;
|
||||||
|
n5 [label="0", shape=box];
|
||||||
|
n5 -> n6;
|
||||||
|
n6 [label="i = 0", shape=box];
|
||||||
|
a6 [label="LivingVariables\nIn := [('', 's')]\nOut := [('', 'i'), ('', 's')]", shape=note, style="filled", fillcolor="#c3b2f7", color="#633fd1", fontcolor="#633fd1"];
|
||||||
|
a6 -> n6 [style=dotted, arrowhead=none, color="#633fd1"];
|
||||||
|
n6 -> n7;
|
||||||
|
n7 [label="0", shape=box];
|
||||||
|
n7 -> n8;
|
||||||
|
n8 [label="j = 0", shape=box];
|
||||||
|
a8 [label="LivingVariables\nIn := [('', 'i'), ('', 's')]\nOut := [('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#f7b2c9", color="#d13f70", fontcolor="#d13f70"];
|
||||||
|
a8 -> n8 [style=dotted, arrowhead=none, color="#d13f70"];
|
||||||
|
n8 -> n9;
|
||||||
|
n9 [label="i", shape=box];
|
||||||
|
n9 -> n10;
|
||||||
|
n10 [label="j", shape=box];
|
||||||
|
n10 -> n11;
|
||||||
|
n11 [label="i + j", shape=box];
|
||||||
|
n11 -> n12;
|
||||||
|
n12 [label="t = (i + j)", shape=box];
|
||||||
|
a12 [label="LivingVariables\nIn := [('', 'i'), ('', 'j'), ('', 's')]\nOut := [('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#b2f7d5", color="#3fd188", fontcolor="#3fd188"];
|
||||||
|
a12 -> n12 [style=dotted, arrowhead=none, color="#3fd188"];
|
||||||
|
n12 -> n13;
|
||||||
|
n13 [label="6", shape=box];
|
||||||
|
n13 -> n14;
|
||||||
|
n14 [label="f = 6", shape=box];
|
||||||
|
a14 [label="LivingVariables\nIn := [('', 'i'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#b2b8f7", color="#3f4bd1", fontcolor="#3f4bd1"];
|
||||||
|
a14 -> n14 [style=dotted, arrowhead=none, color="#3f4bd1"];
|
||||||
|
n14 -> n16;
|
||||||
|
n16 [label="s", shape=box];
|
||||||
|
n16 -> n17;
|
||||||
|
n17 [label="42", shape=box];
|
||||||
|
n17 -> n18;
|
||||||
|
n18 [label="s < 42", shape=box];
|
||||||
|
a18 [label="LivingVariables\nIn := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#f7e6b2", color="#d1ac3f", fontcolor="#d1ac3f"];
|
||||||
|
a18 -> n18 [style=dotted, arrowhead=none, color="#d1ac3f"];
|
||||||
|
n18 -> n19;
|
||||||
|
n19 [label="<?>", shape=diamond];
|
||||||
|
n19 -> n2 [label="T"];
|
||||||
|
n19 -> n21 [label="F"];
|
||||||
|
n2 [label="END", shape=ellipse, style=filled, color=gray];
|
||||||
|
n21 [label="s", shape=box];
|
||||||
|
n21 -> n22;
|
||||||
|
n22 [label="1", shape=box];
|
||||||
|
n22 -> n23;
|
||||||
|
n23 [label="s - 1", shape=box];
|
||||||
|
n23 -> n24;
|
||||||
|
n24 [label="a = (s - 1)", shape=box];
|
||||||
|
a24 [label="LivingVariables\nIn := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#f7b2f7", color="#d13fd1", fontcolor="#d13fd1"];
|
||||||
|
a24 -> n24 [style=dotted, arrowhead=none, color="#d13fd1"];
|
||||||
|
n24 -> n25;
|
||||||
|
n25 [label="i", shape=box];
|
||||||
|
n25 -> n26;
|
||||||
|
n26 [label="j", shape=box];
|
||||||
|
n26 -> n27;
|
||||||
|
n27 [label="i + j", shape=box];
|
||||||
|
n27 -> n28;
|
||||||
|
n28 [label="f", shape=box];
|
||||||
|
n28 -> n29;
|
||||||
|
n29 [label="1", shape=box];
|
||||||
|
n29 -> n30;
|
||||||
|
n30 [label="f + 1", shape=box];
|
||||||
|
n30 -> n31;
|
||||||
|
n31 [label="(i + j) * (f + 1)", shape=box];
|
||||||
|
n31 -> n32;
|
||||||
|
n32 [label="c = ((i + j) * (f + 1))", shape=box];
|
||||||
|
a32 [label="LivingVariables\nIn := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]\nOut := [('', 'c'), ('', 'f'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#e0b2f7", color="#a03fd1", fontcolor="#a03fd1"];
|
||||||
|
a32 -> n32 [style=dotted, arrowhead=none, color="#a03fd1"];
|
||||||
|
n32 -> n33;
|
||||||
|
n33 [label="c", shape=box];
|
||||||
|
n33 -> n34;
|
||||||
|
n34 [label="0", shape=box];
|
||||||
|
n34 -> n35;
|
||||||
|
n35 [label="c > 0", shape=box];
|
||||||
|
a35 [label="LivingVariables\nIn := [('', 'c'), ('', 'f'), ('', 'j'), ('', 's')]\nOut := [('', 'c'), ('', 'f'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#b2c1f7", color="#3f5ed1", fontcolor="#3f5ed1"];
|
||||||
|
a35 -> n35 [style=dotted, arrowhead=none, color="#3f5ed1"];
|
||||||
|
n35 -> n36;
|
||||||
|
n36 [label="<?>", shape=diamond];
|
||||||
|
n36 -> n40 [label="T"];
|
||||||
|
n36 -> n44 [label="F"];
|
||||||
|
n40 [label="j", shape=box];
|
||||||
|
n40 -> n41;
|
||||||
|
n41 [label="c", shape=box];
|
||||||
|
n41 -> n42;
|
||||||
|
n42 [label="j / c", shape=box];
|
||||||
|
n42 -> n43;
|
||||||
|
n43 [label="j = (j / c)", shape=box];
|
||||||
|
a43 [label="LivingVariables\nIn := [('', 'c'), ('', 'f'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#b2d8f7", color="#3f8fd1", fontcolor="#3f8fd1"];
|
||||||
|
a43 -> n43 [style=dotted, arrowhead=none, color="#3f8fd1"];
|
||||||
|
n43 -> n48;
|
||||||
|
n48 [label="j", shape=box];
|
||||||
|
n48 -> n49;
|
||||||
|
n49 [label="2", shape=box];
|
||||||
|
n49 -> n50;
|
||||||
|
n50 [label="j * 2", shape=box];
|
||||||
|
n50 -> n51;
|
||||||
|
n51 [label="f", shape=box];
|
||||||
|
n51 -> n52;
|
||||||
|
n52 [label="(j * 2) / f", shape=box];
|
||||||
|
n52 -> n53;
|
||||||
|
n53 [label="i = ((j * 2) / f)", shape=box];
|
||||||
|
a53 [label="LivingVariables\nIn := [('', 'f'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#d7b2f7", color="#8d3fd1", fontcolor="#8d3fd1"];
|
||||||
|
a53 -> n53 [style=dotted, arrowhead=none, color="#8d3fd1"];
|
||||||
|
n53 -> n54;
|
||||||
|
n54 [label="s", shape=box];
|
||||||
|
n54 -> n55;
|
||||||
|
n55 [label="1", shape=box];
|
||||||
|
n55 -> n56;
|
||||||
|
n56 [label="s + 1", shape=box];
|
||||||
|
n56 -> n57;
|
||||||
|
n57 [label="s = (s + 1)", shape=box];
|
||||||
|
a57 [label="LivingVariables\nIn := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'i'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#def7b2", color="#9bd13f", fontcolor="#9bd13f"];
|
||||||
|
a57 -> n57 [style=dotted, arrowhead=none, color="#9bd13f"];
|
||||||
|
n57 -> n16;
|
||||||
|
n44 [label="j", shape=box];
|
||||||
|
n44 -> n45;
|
||||||
|
n45 [label="c", shape=box];
|
||||||
|
n45 -> n46;
|
||||||
|
n46 [label="j * c", shape=box];
|
||||||
|
n46 -> n47;
|
||||||
|
n47 [label="i = (j * c)", shape=box];
|
||||||
|
a47 [label="LivingVariables\nIn := [('', 'c'), ('', 'f'), ('', 'j'), ('', 's')]\nOut := [('', 'f'), ('', 'j'), ('', 's')]", shape=note, style="filled", fillcolor="#f7c6b2", color="#d1693f", fontcolor="#d1693f"];
|
||||||
|
a47 -> n47 [style=dotted, arrowhead=none, color="#d1693f"];
|
||||||
|
n47 -> n48;
|
||||||
|
rh4 [shape=point, width=0.05, height=0.05, color="#3fd1b9", fillcolor="#3fd1b9", style=filled];
|
||||||
|
{ rank=same; n4; rh4; }
|
||||||
|
n4:se -> rh4 [color="#3fd1b9", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh4 -> n16:ne [color="#3fd1b9", fontcolor="#3fd1b9", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh4 -> n21:se [color="#3fd1b9", fontcolor="#3fd1b9", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh4 -> n54:ne [color="#3fd1b9", fontcolor="#3fd1b9", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh6 [shape=point, width=0.05, height=0.05, color="#633fd1", fillcolor="#633fd1", style=filled];
|
||||||
|
{ rank=same; n6; rh6; }
|
||||||
|
n6:sw -> rh6 [color="#633fd1", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh6 -> n9:sw [color="#633fd1", fontcolor="#633fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh6 -> n25:sw [color="#633fd1", fontcolor="#633fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh8 [shape=point, width=0.05, height=0.05, color="#d13f70", fillcolor="#d13f70", style=filled];
|
||||||
|
{ rank=same; n8; rh8; }
|
||||||
|
n8:se -> rh8 [color="#d13f70", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh8 -> n10:ne [color="#d13f70", fontcolor="#d13f70", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh8 -> n26:ne [color="#d13f70", fontcolor="#d13f70", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh8 -> n40:ne [color="#d13f70", fontcolor="#d13f70", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh8 -> n44:ne [color="#d13f70", fontcolor="#d13f70", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh8 -> n48:ne [color="#d13f70", fontcolor="#d13f70", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh14 [shape=point, width=0.05, height=0.05, color="#3f4bd1", fillcolor="#3f4bd1", style=filled];
|
||||||
|
{ rank=same; n14; rh14; }
|
||||||
|
n14:se -> rh14 [color="#3f4bd1", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh14 -> n28:ne [color="#3f4bd1", fontcolor="#3f4bd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh14 -> n51:se [color="#3f4bd1", fontcolor="#3f4bd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh32 [shape=point, width=0.05, height=0.05, color="#a03fd1", fillcolor="#a03fd1", style=filled];
|
||||||
|
{ rank=same; n32; rh32; }
|
||||||
|
n32:se -> rh32 [color="#a03fd1", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh32 -> n33:se [color="#a03fd1", fontcolor="#a03fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh32 -> n41:se [color="#a03fd1", fontcolor="#a03fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh32 -> n45:se [color="#a03fd1", fontcolor="#a03fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh43 [shape=point, width=0.05, height=0.05, color="#3f8fd1", fillcolor="#3f8fd1", style=filled];
|
||||||
|
{ rank=same; n43; rh43; }
|
||||||
|
n43:sw -> rh43 [color="#3f8fd1", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh43 -> n26:nw [color="#3f8fd1", fontcolor="#3f8fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh43 -> n40:nw [color="#3f8fd1", fontcolor="#3f8fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh43 -> n44:nw [color="#3f8fd1", fontcolor="#3f8fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh43 -> n48:nw [color="#3f8fd1", fontcolor="#3f8fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh53 [shape=point, width=0.05, height=0.05, color="#8d3fd1", fillcolor="#8d3fd1", style=filled];
|
||||||
|
{ rank=same; n53; rh53; }
|
||||||
|
n53:sw -> rh53 [color="#8d3fd1", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh53 -> n25:sw [color="#8d3fd1", fontcolor="#8d3fd1", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh57 [shape=point, width=0.05, height=0.05, color="#9bd13f", fillcolor="#9bd13f", style=filled];
|
||||||
|
{ rank=same; n57; rh57; }
|
||||||
|
n57:se -> rh57 [color="#9bd13f", style=dashed, penwidth=1.2, arrowhead=none, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh57 -> n16:ne [color="#9bd13f", fontcolor="#9bd13f", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh57 -> n21:se [color="#9bd13f", fontcolor="#9bd13f", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
rh57 -> n54:ne [color="#9bd13f", fontcolor="#9bd13f", fontsize=8, style=dashed, penwidth=1.0, arrowsize=0.6, constraint=false, tailclip=true, headclip=true];
|
||||||
|
}
|
||||||
@@ -148,11 +148,15 @@ class IF(compiler.IF):
|
|||||||
|
|
||||||
class WHILE(compiler.WHILE):
|
class WHILE(compiler.WHILE):
|
||||||
def cfa(self, pred, end = None):
|
def cfa(self, pred, end = None):
|
||||||
|
# Dedicated loop-head so every iteration re-evaluates the full condition.
|
||||||
|
cond_entry = CFG_Node()
|
||||||
|
pred.add_child(cond_entry)
|
||||||
|
|
||||||
if hasattr(self.cond, 'arg1') and hasattr(self.cond, 'arg2'):
|
if hasattr(self.cond, 'arg1') and hasattr(self.cond, 'arg2'):
|
||||||
# This is a comparison operation (e.g., a > b)
|
# This is a comparison operation (e.g., a > b)
|
||||||
|
|
||||||
# Create the condition evaluation nodes
|
# Create the condition evaluation nodes
|
||||||
left_node = self.cond.arg1.cfa(pred)
|
left_node = self.cond.arg1.cfa(cond_entry)
|
||||||
right_node = self.cond.arg2.cfa(left_node)
|
right_node = self.cond.arg2.cfa(left_node)
|
||||||
|
|
||||||
# Create the comparison node and attach
|
# Create the comparison node and attach
|
||||||
@@ -161,7 +165,7 @@ class WHILE(compiler.WHILE):
|
|||||||
right_node.add_child(comp_node)
|
right_node.add_child(comp_node)
|
||||||
else:
|
else:
|
||||||
# This is a simple condition (e.g., constant true/false)
|
# This is a simple condition (e.g., constant true/false)
|
||||||
cond_node = self.cond.cfa(pred)
|
cond_node = self.cond.cfa(cond_entry)
|
||||||
comp_node = cond_node
|
comp_node = cond_node
|
||||||
|
|
||||||
# Attach junction node
|
# Attach junction node
|
||||||
@@ -175,11 +179,8 @@ class WHILE(compiler.WHILE):
|
|||||||
# The body should connect back to the start of condition evaluation
|
# The body should connect back to the start of condition evaluation
|
||||||
body_end = self.body.cfa(body_entry)
|
body_end = self.body.cfa(body_entry)
|
||||||
if body_end is not None:
|
if body_end is not None:
|
||||||
# Connect the body end back to the condition evaluation
|
# Connect loop body back to full condition evaluation.
|
||||||
if hasattr(self.cond, 'arg1') and hasattr(self.cond, 'arg2'):
|
body_end.add_child(cond_entry)
|
||||||
body_end.add_child(left_node)
|
|
||||||
else:
|
|
||||||
body_end.add_child(pred)
|
|
||||||
|
|
||||||
# Attach joining node
|
# Attach joining node
|
||||||
after = CFG_Node()
|
after = CFG_Node()
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
digraph CFG {
|
||||||
|
// Analysis: Reached Uses
|
||||||
|
node [fontname="Helvetica"];
|
||||||
|
n1 [label="START", shape=ellipse, style=filled, color=gray];
|
||||||
|
n1 -> n9;
|
||||||
|
n9 [label="2", shape=box];
|
||||||
|
n9 -> n10;
|
||||||
|
n10 [label="CALL g", shape=box, style=filled, color=orange];
|
||||||
|
n10 -> n3;
|
||||||
|
n10 -> n12;
|
||||||
|
n3 [label="START g(x, y)\nRU: [8]", shape=ellipse, style=filled, color=green];
|
||||||
|
n3 -> n6;
|
||||||
|
n6 [label="3", shape=box];
|
||||||
|
n6 -> n7;
|
||||||
|
n7 [label="y = 3\nRU: []", shape=box];
|
||||||
|
n7 -> n8;
|
||||||
|
n8 [label="x", shape=box];
|
||||||
|
n8 -> n4;
|
||||||
|
n4 [label="END g(x, y)", shape=ellipse, style=filled, color=green];
|
||||||
|
n4 -> n12;
|
||||||
|
n12 [label="RET g", shape=box, style=filled, color=orange];
|
||||||
|
n12 -> n2;
|
||||||
|
n2 [label="END", shape=ellipse, style=filled, color=gray];
|
||||||
|
}
|
||||||
@@ -7,9 +7,11 @@ import matplotlib.image as mpimg
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from graphviz import Source
|
from graphviz import Source
|
||||||
|
|
||||||
|
import cfg_build
|
||||||
import lib.console as cnsl
|
import lib.console as cnsl
|
||||||
import syntax
|
import syntax
|
||||||
import triplayacc as yacc
|
import triplayacc as yacc
|
||||||
|
from cfa.analysis_dot import analysis_to_dot, run_all_analyses
|
||||||
from cfg.CFG import CFG
|
from cfg.CFG import CFG
|
||||||
from vistram.tram import *
|
from vistram.tram import *
|
||||||
from vistram.vistram import MachineUI
|
from vistram.vistram import MachineUI
|
||||||
@@ -63,13 +65,54 @@ def pretty_print(node, indent=0):
|
|||||||
print(f"{prefix} {key}: {value}")
|
print(f"{prefix} {key}: {value}")
|
||||||
|
|
||||||
|
|
||||||
|
def print_analysis_reports(cfg, analyses: dict, ru_edges: dict[int, list[int]]):
|
||||||
|
"""Print compact Live Variables and Reached Uses reports to console."""
|
||||||
|
lv = analyses["lv"]
|
||||||
|
ru = analyses["ru"]
|
||||||
|
_ = ru
|
||||||
|
node_by_id = {n.id: n for n in cfg.nodes()}
|
||||||
|
|
||||||
|
def node_text(nid: int) -> str:
|
||||||
|
node = node_by_id.get(nid)
|
||||||
|
if node is None:
|
||||||
|
return "<unknown>"
|
||||||
|
lbl = node.dot_label()
|
||||||
|
return str(lbl) if lbl is not None else "<no-label>"
|
||||||
|
|
||||||
|
print("\nLive Variables Report")
|
||||||
|
print("---------------------")
|
||||||
|
node_ids = sorted(set(lv.in_sets.keys()) | set(lv.out_sets.keys()))
|
||||||
|
for nid in node_ids:
|
||||||
|
in_set = sorted(lv.in_sets.get(nid, set()))
|
||||||
|
out_set = sorted(lv.out_sets.get(nid, set()))
|
||||||
|
if not in_set and not out_set:
|
||||||
|
continue
|
||||||
|
print(f"n{nid} [{node_text(nid)}]: In={in_set} Out={out_set}")
|
||||||
|
|
||||||
|
print("\nReached Uses Report")
|
||||||
|
print("-------------------")
|
||||||
|
has_ru = False
|
||||||
|
for def_id in sorted(ru_edges):
|
||||||
|
uses = sorted(set(ru_edges[def_id]))
|
||||||
|
if not uses:
|
||||||
|
continue
|
||||||
|
has_ru = True
|
||||||
|
use_desc = [f"n{uid} [{node_text(uid)}]" for uid in uses]
|
||||||
|
print(f"n{def_id} [{node_text(def_id)}] -> {use_desc}")
|
||||||
|
if not has_ru:
|
||||||
|
print("(no reached uses)")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
|
print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
mode = cnsl.prompt_choice("\nSelect action:", ["Parse .tripla", "Compile .tripla", "CFG for .tripla", "Exit"])
|
mode = cnsl.prompt_choice(
|
||||||
|
"\nSelect action:",
|
||||||
|
["Parse .tripla", "Compile .tripla", "CFG for .tripla", "Analyze .tripla", "Exit"],
|
||||||
|
)
|
||||||
|
|
||||||
if mode == 3:
|
if mode == 4:
|
||||||
print("\nBye Bye.")
|
print("\nBye Bye.")
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -157,6 +200,7 @@ if __name__ == "__main__":
|
|||||||
filename = default
|
filename = default
|
||||||
|
|
||||||
out_path = Path(__file__).parent / 'cfgdots' / filename
|
out_path = Path(__file__).parent / 'cfgdots' / filename
|
||||||
|
out_path.parent.mkdir(exist_ok=True)
|
||||||
with open(out_path, "w") as f:
|
with open(out_path, "w") as f:
|
||||||
f.write(dot_str)
|
f.write(dot_str)
|
||||||
|
|
||||||
@@ -166,6 +210,46 @@ if __name__ == "__main__":
|
|||||||
render_diagram(dot_str)
|
render_diagram(dot_str)
|
||||||
print("Rendered CFG diagram.")
|
print("Rendered CFG diagram.")
|
||||||
|
|
||||||
|
elif mode == 3:
|
||||||
|
# Reset global CFG builder state so each analysis run is clean
|
||||||
|
cfg_build.FUNCTIONS.clear()
|
||||||
|
cfg_build.CURRENT_FUNCTION = None
|
||||||
|
|
||||||
|
cfg = make_cfg(ast)
|
||||||
|
analysis_name = "Live Variables + Reached Uses"
|
||||||
|
print(f"\nRunning {analysis_name} …")
|
||||||
|
try:
|
||||||
|
_analyses, annotations, ru_edges = run_all_analyses(cfg)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Analysis failed: {exc}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Done. {len(annotations)} LV annotation node(s), "
|
||||||
|
f"{len(ru_edges)} RU definition node(s)."
|
||||||
|
)
|
||||||
|
|
||||||
|
dot_str = analysis_to_dot(cfg, annotations, analysis_name, ru_edges)
|
||||||
|
|
||||||
|
if cnsl.prompt_confirmation("\nPrint analysis reports to console?"):
|
||||||
|
print_analysis_reports(cfg, _analyses, ru_edges)
|
||||||
|
|
||||||
|
if cnsl.prompt_confirmation("\nExport annotated CFG as .dot file?"):
|
||||||
|
default = f"{path.stem}_analysis.dot"
|
||||||
|
filename = input(f"Filename [{default}]: ").strip()
|
||||||
|
if not filename:
|
||||||
|
filename = default
|
||||||
|
|
||||||
|
out_dir = Path(__file__).parent / "cfadots"
|
||||||
|
out_dir.mkdir(exist_ok=True)
|
||||||
|
out_path = out_dir / filename
|
||||||
|
try:
|
||||||
|
with open(out_path, "w") as f:
|
||||||
|
f.write(dot_str)
|
||||||
|
print(f"Saved annotated DOT file as: {out_path}")
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"Could not save DOT file: {exc}")
|
||||||
|
|
||||||
|
if cnsl.prompt_confirmation("Display annotated CFG diagram?"):
|
||||||
|
render_diagram(dot_str)
|
||||||
|
print("Rendered annotated CFG diagram.")
|
||||||
|
|||||||
12
Project-02-03-04-05/triplaprograms/dataflow.tripla
Normal file
12
Project-02-03-04-05/triplaprograms/dataflow.tripla
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
s = 33;
|
||||||
|
i = 0;
|
||||||
|
j = 0;
|
||||||
|
t = i + j;
|
||||||
|
f = 6;
|
||||||
|
while (s < 42) do {
|
||||||
|
a = s - 1;
|
||||||
|
c = (i + j) * (f + 1);
|
||||||
|
if (c > 0) then j = j / c else i = j * c;
|
||||||
|
i = (j * 2) / f;
|
||||||
|
s = s + 1
|
||||||
|
}
|
||||||
9
Project-02-03-04-05/triplaprograms/reached.tripla
Normal file
9
Project-02-03-04-05/triplaprograms/reached.tripla
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
let f(x,y,z) {
|
||||||
|
y = 2;
|
||||||
|
z = 3;
|
||||||
|
let g(x) {
|
||||||
|
x = 7;
|
||||||
|
if (y > 0) then g(y) else x = 8;
|
||||||
|
x
|
||||||
|
} in g(x) + x
|
||||||
|
} in f(2,3)
|
||||||
Reference in New Issue
Block a user