forked from ceu-lang/ceu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfin.lua
More file actions
327 lines (294 loc) · 10.8 KB
/
Copy pathfin.lua
File metadata and controls
327 lines (294 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
function node2blk (node)
if not node.fst then
return _AST.root
elseif node.fst == '_' then
return _AST.root
elseif node.fst == 'global' then
return _AST.root
else
return node.fst.blk
end
end
local AWAITS = {}
F = {
SetExp = function (me)
local op, fr, to = unpack(me)
to = to or _AST.iter'SetBlock'()[1]
local cls = CLS()
local to_blk = node2blk(to)
local req = false
if _TP.deref(to.tp,true) and _TP.deref(fr.tp,true) then
-- "req" has the possibility to be "true"
-- For all "awaits", any pointer assignment requires finalization
if fr.tag=='Ref' and string.sub(fr[1].tag,1,5)=='Await'
or fr.__ast_fr then
req = true
if fr.__ast_fr then
fr = fr.__ast_fr
else
fr = fr[1]
end
-- Normal assignments depend on the __depths
else
if fr.__ast_fr then
-- (a,b) = await X;
----
-- var t* p; -- wrong scope (p is a local)
-- p = await X; -- right scope (where X is defined)
-- a = p:_1;
-- b = p:_2;
fr = fr.__ast_fr
-- lost pointer "tp"
end
-- var T t with
-- this.x = y; -- blk of this? (same as block of t)
-- end;
-- spawn T with
-- this.x = y; -- blk of this? (same as parent spawn/new)
-- end
local constr = _AST.iter'Dcl_constr'()
if constr then
local dcl = _AST.iter'Dcl_var'()
if dcl then
to_blk = dcl.var.blk
else
to_blk = constr.__par.blk
end
end
if fr.tag == 'Op2_call' then
-- Maximum pointer __depth that the function can return.
-- Default is the lowest __depth, i.e., any global pointer.
local fr_max_out = _AST.root
-- Minimum pointer __depth that the function can receive.
-- Default is the same as "to", i.e., as minimum as target variable.
local fr_min_in = to_blk -- max * __depth passed as parameter
local _, _, exps, _ = unpack(fr)
for _, exp in ipairs(exps) do
local blk = node2blk(exp)
if blk.__depth < fr_min_in.__depth then
if not (exp.const or
exp.c and exp.c.mod=='constant') then
fr_min_in = blk
end
end
end
-- pure function never requires finalization
-- int* pa = _fopen(); -- pa(n) fin must consider _RET(_)
if fr.c.mod~='pure' and to_blk.__depth>fr_max_out.__depth then
req = to_blk
end
elseif fr.tag == 'RawExp' then
-- int* pa = { new X() };
if to_blk.__depth > _AST.root.__depth then
req = to_blk
end
else
local fr_blk = node2blk(fr)
-- int a; pa=&a; -- `a´ termination must consider `pa´
if to_blk.__depth < fr_blk.__depth then
req = fr_blk
-- class do int* a1; this.a2=a1; end (a1 is also top-level)
if to_blk.__depth == cls.blk_ifc.__depth and
fr_blk.__depth == cls.blk_body.__depth then
req = false
end
end
end
end
end
-- impossible to run finalizers on threads
if _AST.iter'Thread'() then
req = false
--[[
-- Inside functions the assignment must be from a "hold" parameter
-- to a class field.
-- They cannot have finalizers because different calls will have
-- different scopes for the parameters.
--]]
elseif _AST.iter'Dcl_fun'() then
local dcl = _AST.iter'Dcl_fun'()
if req then
if op ~= ':=' then
-- to a class field
ASR(to_blk == cls.blk_ifc or
to_blk == cls.blk_body,
me, 'invalid attribution')
-- from a parameter
ASR(fr.ref.var and fr.ref.var.funIdx,
me, 'invalid attribution')
-- must be hold
local _, _, ins, _, _, _ = unpack(dcl)
ASR(ins[fr.ref.var.funIdx][1],
me, 'parameter must be `hold´')
end
else
ASR(op == '=', me,
'attribution does not require `finalize´')
end
--[[
-- For awaits, always yield error.
-- Do not allow finalization.
-- Verify if the receiving variable is not accessed after another
-- await.
-- Verify if the receiving variable is acessed in the same block it is
-- defined.
--]]
elseif string.sub(fr.tag,1,5)=='Await' then
if req then
local var = to.ref.var.ast_original_var or to.ref.var
AWAITS[var] = false
ASR(var.blk == _AST.iter'Block'(), me,
'invalid block for awoken pointer "'..var.id..'"')
end
ASR(op ~= ':=', me, 'invalid operator')
else
if req then
ASR((op==':=') or me.fin, me,
'attribution requires `finalize´')
else
ASR((op=='=') and (not me.fin), me,
'attribution does not require `finalize´')
end
if me.fin and me.fin.active then
req.fins = req.fins or {}
table.insert(req.fins, 1, me.fin)
end
end
end,
Var = function (me)
ASR(not AWAITS[me.var], me,
'invalid access to awoken pointer "'..me.var.id..'"')
end,
AwaitInt = function (me)
for var, _ in pairs(AWAITS) do
AWAITS[var] = true
end
end,
AwaitExt = 'AwaitInt',
AwaitT = 'AwaitInt',
AwaitN = 'AwaitInt',
AwaitS = 'AwaitInt',
Finalize_pre = function (me, set, fin)
if not fin then
set, fin = unpack(me)
end
assert(fin[1].tag == 'Block')
assert(fin[1][1].tag == 'Stmts')
fin.active = fin[1] and fin[1][1] and
(#fin[1][1]>1 or
fin[1][1][1] and fin[1][1][1].tag~='Nothing')
if _AST.iter'Dcl_constr'() then
ASR(not fin.active, me,
'only empty finalizers inside constructors')
end
if set then
set.fin = fin -- let call/set handle
elseif fin.active then
local blk = _AST.iter'Block'()
blk.fins = blk.fins or {}
table.insert(blk.fins, 1, fin) -- force finalize for this blk
end
end,
Op2_call_pre = function (me)
local _, f, exps, fin = unpack(me)
if fin then
F.Finalize_pre(me, me, fin)
end
end,
Op2_call = function (me)
local _, f, exps, fin = unpack(me)
local req = false
if not (me.c and (me.c.mod=='pure' or me.c.mod=='nohold')) then
if f.org and string.sub(me.c.id,1,1)=='_' then
--exps = { f.org, unpack(exps) } -- only native
-- avoids this.f(), where f is a pointer to func
-- vs this._f()
end
for i, exp in ipairs(exps) do
local hold = true
if f.var and f.var.fun then
hold,_,_ = unpack(f.var.fun.ins[i])
end
if hold then
-- int* pa; _f(pa);
-- (`pa´ termination must consider `_f´)
local r = exp.fst and (
_TP.deref(exp.tp)
or (_TP.ext(exp.tp) and (not exp.c or
exp.c.mod~='constant'))
or _ENV.clss[_TP.noptr(exp.tp)])
r = r and ((exp.fst=='_' and _AST.root) or exp.fst.blk)
WRN( (not r) or (not req) or (r==req),
me, 'invalid call (multiple scopes)')
req = req or r
end
end
end
if _AST.iter'Thread'() then
req = false -- impossible to run finalizers on threads
end
ASR((not req) or fin or _AST.iter'Dcl_fun'(), me,
'call to "'..me.c.id..'" requires `finalize´')
ASR((not fin) or req, me, 'invalid `finalize´')
if fin and fin.active then
req.fins = req.fins or {}
table.insert(req.fins, 1, fin)
end
end,
}
_AST.visit(F)
--[[
-- EVENTS:
--
-- The event emitter may pass a pointer that is already out of scope when the
-- awaking trail uses it:
--
-- event void* e;
-- var void* v = await e;
-- await ...; // v goes out of scope
-- *v; // segfault
--
-- We have to force the receiving "v" to go out of scope immediatelly:
--
-- event void* e;
-- do
-- var void* v = await e;
-- await ...; // ERROR: cannot inside the "v" enclosing do-end
-- *v;
-- end
--
-------------------------------------------------------------------------------
--
-- FUNCTIONS:
--
-- When holding a parameter, a function could do either on native globals
-- or on object fields:
--
-- function (void* v1, void* v2)=>void f do
-- this.v = v1; // OK
-- _V = v2; // NO!
-- end
--
-- For object fields, the caller must write a finalizer only if the
-- parameter has a shorter scope than the object of the method call:
--
-- // w/o fin
-- var void* p;
-- var T t;
-- t.f(p); // t == p (scope)
--
-- // w/ fin
-- var T t;
-- do
-- var void* p;
-- t.f(p) // t > p (scope)
-- finalize with ... end;
-- end
--
-- Native globals should be forbidden because we would need two different
-- kinds of "nohold" annotations to distinguish the two scopes (object and
-- global).
--
-- Native globals can be assigned in static functions requiring finalizer
-- whenever appliable.
]]