-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReleaseCheck.lean
More file actions
344 lines (317 loc) · 12.4 KB
/
Copy pathReleaseCheck.lean
File metadata and controls
344 lines (317 loc) · 12.4 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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
import Lean
import LeanExe.Extract.Values
open Lean
namespace LeanExe.Extract.Core
def runtimeReleaseArgs? (expr : Expr) : Option (List Expr) :=
match appFnArgs expr.consumeMData with
| (.const name _, args) =>
if name == ``LeanExe.Runtime.release then some args else none
| _ => none
inductive ReleaseProvenance where
| freshAllocation
| freshHelper (name : Name)
| ownerZero
deriving BEq, Repr
def ReleaseProvenance.text : ReleaseProvenance → String
| .freshAllocation => "direct fresh allocation"
| .freshHelper name => s!"fresh helper result from {name}"
| .ownerZero => "statically owner-zero array"
structure ReleaseJudgment where
declaration : Name
expression : String
provenance : ReleaseProvenance
deriving BEq, Repr
inductive ReleaseBindingOrigin where
| accepted (provenance : ReleaseProvenance)
| unresolved (reason : String)
deriving BEq, Repr
structure ReleaseBinding where
name : Name
origin : ReleaseBindingOrigin
escape : Option String := none
deriving BEq, Repr
partial def expressionLabel (ctx : Context) (bindings : List ReleaseBinding) (expr : Expr) : String :=
match expr.consumeMData with
| .bvar index =>
match bindings[index]? with
| some binding => binding.name.toString (escape := false)
| none => s!"bvar {index}"
| _ =>
match appFnArgs expr.consumeMData with
| (.const name _, args) =>
match ctx.env.getProjectionFnInfo? name, args.reverse.head? with
| some _, some target =>
s!"{expressionLabel ctx bindings target}.{name.toString (escape := false)}"
| _, _ => reprStr expr
| _ => reprStr expr
def releaseRootType? (env : Environment) (type : Expr) : Bool :=
match typeAtom? env type with
| some (.array _) => true
| some (.recVariant _ _) => true
| _ => false
def heapBearingType? (env : Environment) (type : Expr) : Bool :=
match typeAtom? env type with
| some ty => !(tyReleaseOwnerSlotOffsets ty).isEmpty
| none => false
def branchSelectedExpr? (value : Expr) : Bool :=
match appFnArgs value.consumeMData with
| (.const name _, _) =>
name == ``ite || name == ``dite ||
match name with
| .str _ component => component.startsWith "match_"
| _ => false
| _ => false
def directFreshReleaseExpr? (ctx : Context) (type value : Expr) : Bool :=
if !releaseRootType? ctx.env type then
false
else
match appFnArgs value.consumeMData with
| (.const name _, _) =>
let freshArrayOperation :=
match typeAtom? ctx.env type with
| some (.array _) =>
[``Array.map, ``Array.append, ``HAppend.hAppend].contains name
| _ => false
freshArrayOperation || name == ``Array.replicate ||
name == ``List.toArray ||
name == ``Array.mk ||
match ctx.env.find? name with
| some (.ctorInfo _) => true
| _ => false
| _ => false
def freshHelperReleaseExpr? (ctx : Context) (value : Expr) : Option Name :=
match appFnArgs value.consumeMData with
| (.const name _, _) => do
let index ← functionIndex? ctx name
let offsets ← ctx.freshResultOwnerOffsets[index]?
if offsets.contains 0 then some name else none
| _ => none
def arraySizeArgument? (expr : Expr) : Option Expr :=
match appFnArgs expr.consumeMData with
| (.const name _, args) =>
if name == ``Array.size then args.reverse.head? else none
| _ => none
def staticallyOwnerZeroExpr? (bindings : List ReleaseBinding) (value : Expr) : Bool :=
match appFnArgs value.consumeMData with
| (.const name _, args) =>
if name != ``Array.setIfInBounds then
false
else
match args.reverse with
| _newValue :: index :: array :: _ =>
match arraySizeArgument? index with
| some sizedArray =>
if sizedArray.consumeMData != array.consumeMData then
false
else
match array.consumeMData with
| .bvar sourceIndex =>
match bindings[sourceIndex]? with
| some { origin := .accepted .ownerZero, .. } => true
| _ => false
| _ => false
| none => false
| _ => false
| _ => false
def conditionallyOwnedArrayExpr? (value : Expr) : Bool :=
match appFnArgs value.consumeMData with
| (.const name _, _) =>
[``Array.pop, ``Array.reverse, ``Array.setIfInBounds].contains name
| _ => false
def releaseOrigin
(ctx : Context)
(bindings : List ReleaseBinding)
(type value : Expr) :
ReleaseBindingOrigin :=
if directFreshReleaseExpr? ctx type value then
.accepted .freshAllocation
else if staticallyOwnerZeroExpr? bindings value then
.accepted .ownerZero
else
match freshHelperReleaseExpr? ctx value with
| some name => .accepted (.freshHelper name)
| none =>
if branchSelectedExpr? value then
.unresolved "ownership is branch-dependent"
else if conditionallyOwnedArrayExpr? value then
.unresolved "the array operation may return either a borrowed or an owned root"
else
match value.consumeMData with
| .proj .. => .unresolved "the released root comes from a structure field"
| .bvar .. => .unresolved "the root is an alias rather than a fresh handoff"
| _ =>
match appFnArgs value.consumeMData with
| (.const name _, _) =>
if ctx.env.getProjectionFnInfo? name |>.isSome then
.unresolved "the released root comes from a structure field"
else
.unresolved s!"{name} has no fresh-root ownership justification"
| _ => .unresolved "the root provenance is unsupported"
def markReleaseBindingEscape
(bindings : List ReleaseBinding)
(index : Nat)
(reason : String) :
List ReleaseBinding :=
bindings.zipIdx.map fun (binding, candidate) =>
if candidate == index then { binding with escape := some reason } else binding
def markHeapBindingEscapes
(ctx : Context)
(name : Name)
(type value : Expr)
(bindings : List ReleaseBinding) :
List ReleaseBinding :=
let independentArray :=
match typeAtom? ctx.env type with
| some (.array item) =>
arrayElementChildMask item == 0 &&
(directFreshReleaseExpr? ctx type value || (freshHelperReleaseExpr? ctx value).isSome)
| _ => false
if !heapBearingType? ctx.env type || independentArray then
bindings
else
bindings.zipIdx.foldl
(fun current item =>
if containsBVar item.snd value then
markReleaseBindingEscape current item.snd
s!"copied into heap-bearing binding {name.toString (escape := false)}"
else
current)
bindings
def releaseCheckError
(declaration : Name)
(expression provenance reason : String) :
String :=
s!"unsafe Runtime.release in {declaration}: released expression {expression}; provenance: {provenance}; reason: {reason}"
def validateReleaseAt
(ctx : Context)
(declaration : Name)
(bindings : List ReleaseBinding)
(args : List Expr)
(body : Expr) :
Except String ReleaseJudgment := do
let (type, value) ←
match args.reverse with
| value :: type :: _ => .ok (type, value)
| _ => .error s!"malformed Runtime.release application in {declaration}"
let label := expressionLabel ctx bindings value
match value.consumeMData with
| .bvar index =>
let binding ←
match bindings[index]? with
| some binding => .ok binding
| none =>
.error (releaseCheckError declaration label "unbound" "the released variable is unbound")
if containsBVar (index + 1) body then
.error <| releaseCheckError declaration label
(match binding.origin with
| .accepted provenance => provenance.text
| .unresolved reason => reason)
"the root is used after release or released more than once"
else
match binding.escape with
| some reason =>
.error <| releaseCheckError declaration label "escaped root" reason
| none =>
match binding.origin with
| .accepted provenance =>
.ok { declaration := declaration, expression := label, provenance := provenance }
| .unresolved reason =>
.error <| releaseCheckError declaration label "unresolved" reason
| _ =>
match releaseOrigin ctx bindings type value with
| .accepted provenance =>
.ok { declaration := declaration, expression := label, provenance := provenance }
| .unresolved reason =>
.error <| releaseCheckError declaration label "unresolved" reason
mutual
partial def validateReleaseExpr
(ctx : Context)
(declaration : Name)
(bindings : List ReleaseBinding)
(expr : Expr) :
Except String (Array ReleaseJudgment) := do
match expr.consumeMData with
| .letE name type value body _ =>
match runtimeReleaseArgs? value with
| some args =>
let judgment ← validateReleaseAt ctx declaration bindings args body
let bodyJudgments ←
validateReleaseExpr ctx declaration
({ name := name, origin := .unresolved "release result is scalar" } :: bindings)
body
.ok (#[judgment] ++ bodyJudgments)
| none =>
let valueJudgments ← validateReleaseExpr ctx declaration bindings value
let escapedBindings := markHeapBindingEscapes ctx name type value bindings
let binding : ReleaseBinding :=
{ name := name, origin := releaseOrigin ctx escapedBindings type value }
let bodyJudgments ←
validateReleaseExpr ctx declaration (binding :: escapedBindings) body
.ok (valueJudgments ++ bodyJudgments)
| .lam name _ body _ =>
validateReleaseExpr ctx declaration
({ name := name, origin := .unresolved "ownership comes from a function parameter" } :: bindings)
body
| .app fn arg =>
match runtimeReleaseArgs? expr with
| some _ =>
.error <| releaseCheckError declaration (expressionLabel ctx bindings expr) "unsupported"
"Runtime.release must be the complete value of a let binding"
| none =>
let fnJudgments ← validateReleaseExpr ctx declaration bindings fn
let argJudgments ← validateReleaseExpr ctx declaration bindings arg
.ok (fnJudgments ++ argJudgments)
| .proj _ _ value => validateReleaseExpr ctx declaration bindings value
| _ => .ok #[]
end
partial def validateReleaseDeclaration
(ctx : Context)
(entry declaration : Name)
(bindings : List ReleaseBinding)
(expr : Expr) :
Except String (Array ReleaseJudgment) := do
match expr.consumeMData with
| .lam name type body _ =>
let origin :=
if declaration == entry && releaseRootType? ctx.env type then
.accepted .ownerZero
else
.unresolved "ownership comes from a function parameter"
validateReleaseDeclaration ctx entry declaration ({ name := name, origin := origin } :: bindings) body
| _ => validateReleaseExpr ctx declaration bindings expr
def generatedRecursionHelperValue?
(ctx : Context)
(name : Name)
(info : ConstantInfo) :
Option Expr := do
let value ← info.value?
let helperName := .str name "_f"
if !value.getUsedConstants.contains helperName then
none
else
let helperInfo ← ctx.env.find? helperName
let helperValue ← helperInfo.value?
some (betaSpecializeExpr ctx.env ctx.root 32 helperValue)
def validateModuleReleases
(ctx : Context)
(entry : Name)
(names : List Name) :
Except String (Array ReleaseJudgment) := do
let mut judgments := #[]
for name in names do
let info ←
match ctx.env.find? name with
| some info => .ok info
| none => .error s!"declaration disappeared during release checking: {name}"
match info.value? with
| some value =>
let specialized := betaSpecializeExpr ctx.env ctx.root 32 value
judgments := judgments ++ (← validateReleaseDeclaration ctx entry name [] specialized)
match generatedRecursionHelperValue? ctx name info with
| some helperValue =>
judgments := judgments ++
(← validateReleaseDeclaration ctx entry name [] helperValue)
| none => pure ()
| none => pure ()
.ok judgments
end LeanExe.Extract.Core