Skip to content
← Back

src/syntax/process/mark_boxed_locals.ghul

1
namespace Syntax.Process is
2
use IO.Std;
3
4
use Logging;
5
6
use Semantic.Symbols.LOCAL_VARIABLE;
7
8
// Pre-`compile-expressions` analysis pass: walks the AST,
9
// resolves every IDENTIFIER reference and ASSIGNMENT LHS,
10
// marks each `LOCAL_VARIABLE` it sees as captured (if
11
// referenced from inside an enclosing function — i.e. from
12
// a closure body) and/or reassigned (if it appears on the
13
// LHS of an assignment). At end of apply, sweeps the set of
14
// touched locals and sets `is_boxed = is_captured /\
15
// is_reassigned`, so that subsequent `symbol_loader`-driven
16
// IR emission switches to `Ghul.BOX[T]`-backed storage.
17
//
18
// Runs only on IL-bound builds: boxing is a code-generation
19
// strategy, and assignability of a captured local is keyed
20
// on its mut marker alone, so analysis-only paths never need
21
// the flags this pass derives.
22
class MARK_BOXED_LOCALS: ScopedVisitor is
23
_logger: Logger;
24
_touched: Collections.SET[LOCAL_VARIABLE];
25
26
init(
27
logger: Logger,
28
symbol_table: Semantic.SYMBOL_TABLE,
29
namespaces: Semantic.NAMESPACES
30
)
31
is
32
super.init(logger, symbol_table, namespaces);
33
34
_logger = logger;
35
_touched = Collections.SET[LOCAL_VARIABLE]();
36
si
37
38
apply(root: Trees.Node) is
39
_touched.clear();
40
41
root.walk(self);
42
43
// Sweep: a local is boxed iff (a) it was declared
44
// with the `mut` modifier, (b) it's captured from
45
// inside an enclosing closure body, and (c) it's
46
// assigned anywhere. Plain captured immutable
47
// locals stay value-captured (and any rogue
48
// assignment hits the captured-assignment diagnostic
49
// upstream). Plain mutable-but-not-captured locals
50
// stay on the stack.
51
for local in _touched do
52
if
53
local.is_mutable_marked /\
54
local.is_captured /\
55
local.is_reassigned
56
then
57
local.is_boxed = true;
58
fi
59
od
60
61
_touched.clear();
62
si
63
64
// Identifier-as-expression reference. If it resolves to
65
// a LOCAL_VARIABLE owned by a function outside our
66
// current function context, this is a capture.
67
visit(identifier: Trees.Expressions.IDENTIFIER) is
68
69
let symbol = try_find(identifier.identifier);
70
71
if !symbol? \/ !isa LOCAL_VARIABLE(symbol) then
72
return;
73
fi
74
75
let local = cast LOCAL_VARIABLE(symbol);
76
let function = current_function;
77
78
if !function? then
79
return;
80
fi
81
82
_touched.add(local);
83
84
if local.owner != function then
85
local.is_captured = true;
86
fi
87
si
88
89
// Reassignment of a local: LHS resolves to a
90
// LOCAL_VARIABLE. Field assignments (`obj.field = v`)
91
// and the like get filtered by the LOCAL_VARIABLE cast.
92
visit(assignment: Trees.Statements.ASSIGNMENT) is
93
let targets = Collections.LIST[Trees.Expressions.Expression]();
94
assignment.left.get_names_into(targets);
95
96
for target in targets do
97
if !isa Trees.Expressions.IDENTIFIER(target) then
98
continue;
99
fi
100
101
let symbol = try_find(target.identifier);
102
103
if !symbol? \/ !isa LOCAL_VARIABLE(symbol) then
104
continue;
105
fi
106
107
_touched.add(symbol);
108
symbol.is_reassigned = true;
109
od
110
si
111
si
112
si