-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathValidity.lean
More file actions
368 lines (312 loc) · 14.1 KB
/
Copy pathValidity.lean
File metadata and controls
368 lines (312 loc) · 14.1 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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
module
public import Veir.PatternRewriter.Puddle.Builders
/-!
# Puddle Patterns Validity
This file defines the obligations for a Puddle pattern to be considered valid (`Pattern.Valid`),
both structurally and semantically. If `Pattern.Valid` holds, then compiling the Puddle pattern
with `Pattern.compile` should produce a rewrite pattern that satisfies `LocalRewritePattern.Valid`.
-/
namespace Veir.Puddle
public section
variable {OpInfo : Type} [HasOpInfo OpInfo]
/-!
## Supported Opcodes
Puddle currently only supports operations that cannot terminate a block and have no memory effects.
-/
/--
An opcode is supported when it is not a terminator and has no memory effects for any possible
property value.
We could in the future support opcodes when we know that the properties matched or created by the
pattern are such that the operation has no memory effects, but this is only happening in rare cases.
-/
@[expose]
def SupportedOpCode (opCode : OpInfo) : Prop :=
HasOpInfo.isTerminator opCode = false ∧
∀ property, HasOpInfo.getEffects opCode property = .none
/-- A match declaration is supported when the opcode of an operation declaration is supported. -/
@[expose]
def MatchDecl.Supported (decl : MatchDecl OpInfo) : Prop :=
match decl with
| .operation opCode _ _ _ _ _ _ _ => SupportedOpCode opCode
| _ => True
/-- Every declaration in a match program uses supported opcodes. -/
@[expose]
def MatchProg.Supported (prog : MatchProg OpInfo α) : Prop :=
∀ decl ∈ prog.decls, decl.Supported
/-- A creation declaration is supported when the opcode of an operation declaration is supported. -/
@[expose]
def CreateDecl.Supported : CreateDecl OpInfo → Prop
| .operation opCode _ _ _ _ _ => SupportedOpCode opCode
| _ => True
/-- Every declaration in a creation program uses supported opcodes. -/
@[expose]
def CreateProg.Supported (prog : CreateProg OpInfo α) : Prop :=
∀ decl ∈ prog.decls, decl.Supported
/-- The pattern only references supported opcodes. -/
@[expose]
def Pattern.Supported (rule : Pattern OpInfo) : Prop :=
rule.matcher.Supported ∧ rule.creation.Supported
/-! ## Root Constraint -/
/--
The first declaration in the match program is an operation declaration whose operation handle is
the program's distinguished root handle.
-/
@[expose]
def MatchProg.ConstrainsRoot (prog : MatchProg OpInfo α) : Prop :=
match prog.decls with
| .operation _ _ _ _ _ opHandle _ _ :: _ => opHandle = prog.rootHandle
| _ => False
/--
Return the hidden SSA-result handles from the program's root constraint.
The root is assumed to be the first declaration in the match program. If it is not, then this
function returns `none`.
-/
@[expose]
def MatchProg.rootResults? (prog : MatchProg OpCode α) :
Option (Array (Handle OpCode .value)) :=
match prog.decls with
| .operation _ _ _ _ _ opHandle results _ :: _ =>
if opHandle = prog.rootHandle then some results else none
| _ => none
/-!
## Structural well-formedness
This section defines `Pattern.StructurallyWellFormed`, the obligations of a Puddle pattern to be
considered structurally well-formed, and therefore structurally valid. While `Builders` ensure that
the pattern is well-formed, it is still possible to construct a pattern that is not well-formed by
using the underlying `MatchProg` and `CreateProg` constructors directly.
-/
/--
The abstract context of handle identifiers and their runtime kinds.
This context is used to record the handles that have been bound by the matcher and creation phase,
to check at each step if the inputs are available, and if the output is fresh, or was declared with
the same kind.
-/
structure HandleContext where
/- The bindings ids that have been established, with their kinds. -/
bindings : List (Nat × HandleType OpCode)
/-
The bindings that have been established, but that are not allowed to be used.
This is used to mark the root and its results as unavailable to the creation phase and
replacement phase, even though they are still bound by the matcher.
-/
unavailable : List Nat
deriving DecidableEq
namespace HandleContext
/-- The empty handle context. -/
def empty : HandleContext := ⟨[], []⟩
/-- Look up the kind occupying an identifier. -/
def lookup (handles : HandleContext) (id : Nat) : Option (HandleType OpCode) :=
let rec lookupBindings : List (Nat × HandleType OpCode) → Option (HandleType OpCode)
| [] => none
| (definedId, kind) :: rest =>
if definedId = id then some kind else lookupBindings rest
lookupBindings handles.bindings
/-- Succeed exactly when a typed handle has already been defined with precisely that kind. -/
@[expose]
def require (ctx : HandleContext) (handle : Handle OpCode kind) : Bool :=
ctx.lookup handle.id = some kind ∧ handle.id ∉ ctx.unavailable
/-- Require every handle in a homogeneous list to be available. -/
@[expose]
def requireMany (ctx : HandleContext) (handles : List (Handle OpCode kind)) : Bool :=
handles.all ctx.require
/-- Record an output, requiring its identifier to be fresh in the context. -/
def insertFresh (ctx : HandleContext) (handle : Handle OpCode kind) : Option HandleContext :=
match ctx.lookup handle.id with
| none => some ⟨(handle.id, kind) :: ctx.bindings, ctx.unavailable⟩
| some _ => none
/-- Record several outputs, checking freshness between the outputs as well. -/
@[expose]
def insertManyFresh (ctx : HandleContext) (handles : List (Handle OpCode kind))
: Option HandleContext :=
handles.foldlM insertFresh ctx
/-- Mark an allocated handle as unavailable to creation and replacement inputs. -/
def forbid (ctx : HandleContext) (handle : Handle OpCode kind) : HandleContext :=
⟨ctx.bindings, handle.id :: ctx.unavailable⟩
/-- Mark several allocated handles as unavailable while retaining them for freshness checks. -/
@[expose]
def forbidMany (ctx : HandleContext) (handles : List (Handle OpCode kind)) : HandleContext :=
handles.foldl forbid ctx
end HandleContext
namespace MetadataTuple.Shape
/-- Require every handle described by a metadata tuple shape to be available. -/
@[expose]
def requireBindings (shape : MetadataTuple.Shape OpCode Handles) (ctx : HandleContext)
(handles : Handles) : Bool :=
match shape with
| .unit => true
| .atom .type => ctx.require handles
| .atom (.property _) => ctx.require handles
| .cons .type tail => ctx.require handles.1 && tail.requireBindings ctx handles.2
| .cons (.property _) tail => ctx.require handles.1 && tail.requireBindings ctx handles.2
/-- Insert every handle described by a metadata tuple shape, requiring each one to be fresh. -/
@[expose]
def insertFreshBindings (shape : MetadataTuple.Shape OpCode Handles) (ctx : HandleContext)
(handles : Handles) : Option HandleContext :=
match shape with
| .unit => some ctx
| .atom .type => ctx.insertFresh handles
| .atom (.property _) => ctx.insertFresh handles
| .cons .type tail => do
let ctx ← ctx.insertFresh handles.1
tail.insertFreshBindings ctx handles.2
| .cons (.property _) tail => do
let ctx ← ctx.insertFresh handles.1
tail.insertFreshBindings ctx handles.2
end MetadataTuple.Shape
/--
Collect the handles that a matcher declaration binds during a successful match.
Requires that all inputs are available in the context, and that all outputs are fresh.
-/
@[expose]
def MatchDecl.collectBindings (decl : MatchDecl OpCode)
(defined : HandleContext) : Option HandleContext := do
match decl with
| .operation _ operands resultTypes _ propertyHandle opHandle results _ =>
guard (defined.requireMany operands.toList)
guard (defined.requireMany resultTypes.toList)
let defined ← defined.insertManyFresh results.toList
let defined ← defined.insertFresh propertyHandle
defined.insertFresh opHandle
| .value typeHandle result =>
guard (defined.require typeHandle)
defined.insertFresh result
| .type _ result =>
defined.insertFresh result
| @MatchDecl.applyNative _ _ _ inputBundle inputs _ => do
guard (inputBundle.shape.requireBindings defined inputs)
return defined
/--
Collect all the handles that a list of matcher declarations bind during a successful match.
In particular, this includes the root result handles.
-/
@[expose]
def MatchProg.collectDeclBindings :
List (MatchDecl OpCode) → HandleContext → Option HandleContext
| [], defined => some defined
| decl :: decls, defined => do
let defined ← decl.collectBindings defined
MatchProg.collectDeclBindings decls defined
/-- Order the matching declarations from leaves to root, then native guards. -/
@[expose]
def MatchProg.bindingDecls (prog : MatchProg OpInfo α) : List (MatchDecl OpInfo) :=
let (structural, guards) := prog.decls.partition fun
| @MatchDecl.applyNative _ _ _ _ _ _ => false
| _ => true
structural.reverse ++ guards
/--
Collect every available handle that can be bound by a successful matcher, and mark as unavailable
the root operation handle and its result handles.
-/
@[expose]
def MatchProg.collectBindings (prog : MatchProg OpCode α) : Option HandleContext :=
do
let rootResults ← prog.rootResults?
let defined ← MatchProg.collectDeclBindings prog.bindingDecls .empty
let defined := defined.forbid prog.rootHandle
return defined.forbidMany rootResults.toList
/--
Check one creation declaration and extend the context with its outputs. Inputs must be available at
this exact program point, while all outputs must have globally fresh identifiers.
-/
@[expose]
def CreateDecl.checkBindings (ctx : HandleContext) (decl : CreateDecl OpCode)
: Option HandleContext := do
match decl with
| .type _ result =>
ctx.insertFresh result
| .property _ _ result =>
ctx.insertFresh result
| .operation _ operands resultTypes properties opHandle resultHandles =>
guard (ctx.requireMany operands.toList)
guard (ctx.requireMany resultTypes.toList)
guard (ctx.require properties)
guard (resultHandles.size = resultTypes.size)
let defined ← ctx.insertFresh opHandle
defined.insertManyFresh resultHandles.toList
| @CreateDecl.applyNative _ _ _ _ inputBundle outputBundle inputs _ outputs => do
guard (inputBundle.shape.requireBindings ctx inputs)
outputBundle.shape.insertFreshBindings ctx outputs
/-- Validate a creation program from a matcher-defined handle context. -/
@[expose]
def CreateProg.checkBindings (ctx : HandleContext) (prog : CreateProg OpCode α)
: Option HandleContext :=
prog.decls.foldlM CreateDecl.checkBindings ctx
/-- Require every replacement value to be available after the creation program. -/
@[expose]
def Replacement.checkBindings (replacement : Replacement OpCode)
(ctx : HandleContext) : Bool :=
ctx.requireMany replacement.values.toList
/-- Run the complete structural checker on a pattern. -/
@[expose]
def Pattern.checkStructure (rule : Pattern OpCode) : Option HandleContext := do
let defined ← rule.matcher.collectBindings
let defined ← rule.creation.checkBindings defined
guard (rule.replacement.checkBindings defined)
return defined
/--
Structural validity of a Puddle pattern. It checks that:
* the match program executes an operation declaration for its root first;
* when the declarations are processed in reverse order, inputs are introduced before
use and outputs are fresh
* every creation input is bound by the matcher or by an earlier creation declaration;
* every creation output has an identifier that is globally fresh;
* each created operation has as many result handles as result-type handles;
* every replacement value is bound by the matcher or the creation program; and
* the matched root and its results are not available to the creation or replacement phases.
-/
@[expose]
def Pattern.StructurallyWellFormed (rule : Pattern OpCode) : Prop :=
rule.checkStructure.isSome = true
instance (rule : Pattern OpCode) : Decidable rule.StructurallyWellFormed := by
unfold Pattern.StructurallyWellFormed
infer_instance
/-!
## Pattern Validity
`Pattern.Valid` is the predicate that a Puddle pattern is both sound structurally and
semantically. If `Pattern.Valid` holds, then compiling the Puddle pattern
with `Pattern.compile` should produce a rewrite pattern that satisfies `LocalRewritePattern.Valid`.
-/
/-- The static validity conditions required by a Puddle pattern. -/
structure Pattern.Valid (rule : Pattern OpCode) : Prop where
/-- Every operation declaration in the pattern uses a supported opcode. -/
Supported : rule.Supported
/-- The first executed declaration constrains the match program's root handle. -/
ConstrainsRoot : rule.matcher.ConstrainsRoot
/-- Structural validity of the pattern. -/
structurallyWellFormed : rule.StructurallyWellFormed
end
/-!
## Validity Tactics
This section defines tactics for proving the different obligations of `Pattern.Valid`. These tactics
are intended to be used in the proof of `Pattern.Valid` for a specific Puddle pattern.
-/
/-- Unfold and simplify the builders used to construct a concrete Puddle pattern. -/
macro "unfoldPuddleBuilder" : tactic =>
`(tactic| (
/- Unfold the builder functions -/
simp only [Pattern.Builder, MatchProg.build, CreateProg.build, bind, pure,
MatchProg.value, MatchProg.type, MatchProg.root, MatchProg.operation, MatchProg.matchNative,
CreateProg.operation, CreateProg.property, CreateProg.applyNative, MetadataTuple.fresh,
MetadataTuple.Shape.fresh, MetadataTuple.Atom.fresh,
/- Simplify the resulting expressions with standard simplifications -/
Nat.zero_add, Nat.reduceAdd, List.size_toArray, List.length_cons, List.length_nil,
Array.size_map, Array.size_range, Nat.lt_add_one, getElem!_pos, Array.getElem_map,
Array.getElem_range, Nat.add_zero, List.cons_append, List.nil_append, List.reverse_nil,
List.reverse_cons, List.reverse_nil, List.nil_append, List.cons_append]))
/-- Prove a `Puddle.Supported` goal. -/
macro "provePuddleSupported" : tactic =>
`(tactic| (
simp [Pattern.Supported, CreateProg.Supported, MatchProg.Supported, MatchDecl.Supported,
CreateDecl.Supported, SupportedOpCode, get_effects, is_terminator];
done
))
/-- Prove a `Puddle.Valid` goal. -/
macro "provePuddleValid" : tactic =>
`(tactic| (
unfoldPuddleBuilder
constructor
· provePuddleSupported
· cbv
· cbv
))
end Veir.Puddle