Skip to content
← Back

src/syntax/process/store_free_fixpoint.ghul

1
namespace Syntax.Process is
2
use Function = Semantic.Symbols.Function;
3
use Semantic.Symbols.STORE_FREE_IMPORTS;
4
5
// Solves the per-function facts gathered by INFER_STORE_FREE's
6
// body walk and writes every function's is_store_free bit.
7
//
8
// A function is unsafe if its body disqualified it, if a call
9
// bound to it could dispatch to an override outside the
10
// compilation, or if any bounded callee or any override of it is
11
// unsafe; unsafety propagates to a fixpoint and everything never
12
// reached is store-free. Mutual recursion between clean bodies is
13
// never seeded, so it correctly ends up store-free.
14
class STORE_FREE_FIXPOINT is
15
init() is
16
_flipped = Collections.LIST[Function]();
17
si
18
19
// Every function whose store-free bit ended the most recent mark
20
// holding a different value than it did before — the signal an
21
// incremental analysis-mode compile uses to tell that callers'
22
// narrowings computed against the old bits are now stale, and
23
// the set it can bound a targeted true-up to.
24
flipped: Collections.List[Function] => _flipped;
25
_flipped: Collections.LIST[Function];
26
27
// Returns whether any function's store-free bit ended this run
28
// holding a different value than it did before.
29
mark(facts: Collections.MutableMap[Function, STORE_FREE_FACTS]) -> bool is
30
_flipped = Collections.LIST[Function]();
31
32
let unsafe = Collections.SET[Function]();
33
34
for function in facts.keys do
35
if facts[function].is_disqualified \/ is_openly_dispatchable(function) then
36
unsafe.add(function);
37
fi
38
od
39
40
let changed mut = true;
41
42
while changed do
43
changed = false;
44
45
for function in facts.keys do
46
if !unsafe.contains(function) /\ !_all_dependencies_safe(function, facts, unsafe) then
47
unsafe.add(function);
48
changed = true;
49
fi
50
od
51
od
52
53
let any_bit_changed mut = false;
54
55
for function in facts.keys do
56
if _assign(function, !unsafe.contains(function)) then
57
_flipped.add(function);
58
any_bit_changed = true;
59
fi
60
od
61
62
// Trusted imports referenced this compilation get the bit
63
// too — the per-call-site kill test reads the callee's
64
// bit directly, without consulting the trust list — and
65
// lose it again if an in-assembly override of a virtual
66
// import turned unsafe.
67
for function in facts.keys do
68
for callee in facts[function].callees do
69
if !facts.contains_key(callee) then
70
if _assign(callee, _target_is_safe(callee, facts, unsafe)) then
71
_flipped.add(callee);
72
any_bit_changed = true;
73
fi
74
fi
75
od
76
od
77
78
// A constructor writes only the fresh object it builds, so
79
// it is construction-store-free — the bit `NEW` reads — when
80
// nothing but its own instance-field writes disqualified it
81
// and every callee it depends on proved store-free. The
82
// strict bit above stays the answer at every other call
83
// site, so `obj.init(...)` on a pre-existing receiver is
84
// unaffected. Constructors never dispatch openly, so no
85
// override shadow is in play.
86
for function in facts.keys do
87
if function.is_constructor then
88
if
89
_assign_constructs(
90
function,
91
!facts[function].is_construction_disqualified /\
92
_all_dependencies_safe(function, facts, unsafe)
93
)
94
then
95
_flipped.add(function);
96
any_bit_changed = true;
97
fi
98
fi
99
od
100
101
return any_bit_changed;
102
si
103
104
// Sets the bit and reports whether that moved its effective
105
// value. The read and write both route through
106
// root_specialized_from, so specialisations sharing a root
107
// compare and write the one bit consistently.
108
_assign(function: Function, value: bool) -> bool is
109
let was = function.is_store_free;
110
function.set_store_free(value);
111
return function.is_store_free != was;
112
si
113
114
// The construction-bit equivalent: a constructor that stays
115
// strictly not-store-free can still flip construction-store-free
116
// when an edit adds or removes a foreign store. The move has to
117
// reach `_flipped` so the incremental true-up recompiles the
118
// NEW-site callers whose narrowings were computed against the
119
// old bit; without it a true→false flip retains stale facts.
120
_assign_constructs(function: Function, value: bool) -> bool is
121
let was = function.constructs_store_free;
122
function.set_constructs_store_free(value);
123
return function.constructs_store_free != was;
124
si
125
126
_all_dependencies_safe(
127
function: Function,
128
facts: Collections.MutableMap[Function, STORE_FREE_FACTS],
129
unsafe: Collections.SET[Function]
130
) -> bool is
131
for callee in facts[function].callees do
132
if !_target_is_safe(callee, facts, unsafe) then
133
return false;
134
fi
135
od
136
137
// a call bound to this function may dispatch to any
138
// override of it
139
let overriders = function.overriders;
140
141
if overriders? then
142
for overrider in overriders do
143
if !isa Function(overrider) then
144
return false;
145
fi
146
147
let overrider_function = cast Function(overrider);
148
149
if !facts.contains_key(overrider_function) \/ unsafe.contains(overrider_function) then
150
return false;
151
fi
152
od
153
fi
154
155
return true;
156
si
157
158
// A call target is safe when it was walked and has not been
159
// demoted, or when it is a trusted store-free import. A
160
// trusted import can still be virtual (object.to_string), so
161
// its in-assembly overriders — which have facts entries and
162
// their own fixpoint rows — are part of the dispatch shadow
163
// and must be safe too.
164
_target_is_safe(
165
target: Function,
166
facts: Collections.MutableMap[Function, STORE_FREE_FACTS],
167
unsafe: Collections.SET[Function]
168
) -> bool is
169
// Declared `pure` is a trust declaration — it holds even
170
// when the body is unprovable, and the pure-override
171
// contract separately diagnoses any overrider that
172
// breaks it, so the dispatch shadow needs no re-check
173
// here.
174
if target.is_declared_pure then
175
return true;
176
fi
177
178
if facts.contains_key(target) then
179
return !unsafe.contains(target);
180
fi
181
182
if !STORE_FREE_IMPORTS.is_store_free(target) then
183
return false;
184
fi
185
186
let target_overriders = target.overriders;
187
188
if target_overriders? then
189
for overrider in target_overriders do
190
if !isa Function(overrider) then
191
return false;
192
fi
193
194
let overrider_function = cast Function(overrider);
195
196
if !facts.contains_key(overrider_function) \/ unsafe.contains(overrider_function) then
197
return false;
198
fi
199
od
200
fi
201
202
return true;
203
si
204
205
// A call bound to this function may dispatch to an override
206
// this compilation cannot see when the owner hierarchy is
207
// open to other assemblies. Struct methods are final, and
208
// constructors and static/global functions do not dispatch.
209
is_openly_dispatchable(function: Function) -> bool is
210
if isa Semantic.Symbols.STRUCT_METHOD(function) then
211
return false;
212
fi
213
214
if !isa Semantic.Symbols.INSTANCE_METHOD(function) then
215
return false;
216
fi
217
218
if function.name =~ "init" then
219
return false;
220
fi
221
222
let owner = function.owner;
223
224
if !owner? \/ !isa Semantic.Symbols.Classy(owner) then
225
return true;
226
fi
227
228
return (cast Semantic.Symbols.Classy(owner)).is_open;
229
si
230
si
231
si