comparison src/sqlcache.sml @ 2215:639e62ca2530

Mostly finish effectfulness analysis.
author Ziv Scully <ziv@mit.edu>
date Fri, 31 Oct 2014 09:25:03 -0400
parents 365727ff68f4
children 70ec9bb337be
comparison
equal deleted inserted replaced
2214:edd634b889d0 2215:639e62ca2530
13 (* Filled in by cacheWrap during Sqlcache. *) 13 (* Filled in by cacheWrap during Sqlcache. *)
14 val ffiInfo : {index : int, params : int} list ref = ref [] 14 val ffiInfo : {index : int, params : int} list ref = ref []
15 15
16 fun getFfiInfo () = !ffiInfo 16 fun getFfiInfo () = !ffiInfo
17 17
18 (* Program analysis. *) 18 (* Some FFIs have writing as their only effect, which the caching records. *)
19 val ffiEffectful =
20 let
21 val fs = SS.fromList ["htmlifyInt_w",
22 "htmlifyFloat_w",
23 "htmlifyString_w",
24 "htmlifyBool_w",
25 "htmlifyTime_w",
26 "attrifyInt_w",
27 "attrifyFloat_w",
28 "attrifyString_w",
29 "attrifyChar_w",
30 "urlifyInt_w",
31 "urlifyFloat_w",
32 "urlifyString_w",
33 "urlifyBool_w",
34 "urlifyChannel_w"]
35 in
36 fn (m, f) => Settings.isEffectful (m, f)
37 andalso not (m = "Basis" andalso SS.member (fs, f))
38 end
39
40
41 (* Effect analysis. *)
42
43 (* Makes an exception for EWrite (which is recorded when caching). *)
44 fun effectful doPrint (effs : IS.set) (inFunction : bool) (bound : int) : Mono.exp -> bool =
45 (* If result is true, expression is definitely effectful. If result is
46 false, then expression is definitely not effectful if effs is fully
47 populated. The intended pattern is to use this a number of times equal
48 to the number of declarations in a file, Bellman-Ford style. *)
49 (* TODO: make incrementing of bound less janky, probably by using MonoUtil
50 instead of all this. *)
51 let
52 (* DEBUG: remove printing when done. *)
53 fun tru msg = if doPrint then (print (msg ^ "\n"); true) else true
54 val rec eff' =
55 (* ASK: is there a better way? *)
56 fn EPrim _ => false
57 (* We don't know if local functions have effects when applied. *)
58 | ERel idx => if inFunction andalso idx >= bound
59 then tru ("rel" ^ Int.toString idx) else false
60 | ENamed name => if IS.member (effs, name) then tru "named" else false
61 | ECon (_, _, NONE) => false
62 | ECon (_, _, SOME e) => eff e
63 | ENone _ => false
64 | ESome (_, e) => eff e
65 (* TODO: use FFI whitelist. *)
66 | EFfi (m, f) => if ffiEffectful (m, f) then tru "ffi" else false
67 | EFfiApp (m, f, _) => if ffiEffectful (m, f) then tru "ffiapp" else false
68 (* ASK: we're calling functions effectful if they have effects when
69 applied or if the function expressions themselves have effects.
70 Is that okay? *)
71 (* This is okay because the values we ultimately care about aren't
72 functions, and this is a conservative approximation, anyway. *)
73 | EApp (eFun, eArg) => effectful doPrint effs true bound eFun orelse eff eArg
74 | EAbs (_, _, _, e) => effectful doPrint effs inFunction (bound+1) e
75 | EUnop (_, e) => eff e
76 | EBinop (_, _, e1, e2) => eff e1 orelse eff e2
77 | ERecord xs => List.exists (fn (_, e, _) => eff e) xs
78 | EField (e, _) => eff e
79 (* If any case could be effectful, consider it effectful. *)
80 | ECase (e, xs, _) => eff e orelse List.exists (fn (_, e) => eff e) xs
81 | EStrcat (e1, e2) => eff e1 orelse eff e2
82 (* ASK: how should we treat these three? *)
83 | EError _ => tru "error"
84 | EReturnBlob _ => tru "blob"
85 | ERedirect _ => tru "redirect"
86 (* EWrite is a special exception because we record writes when caching. *)
87 | EWrite _ => false
88 | ESeq (e1, e2) => eff e1 orelse eff e2
89 (* TODO: keep context of which local variables aren't effectful? Only
90 makes a difference for function expressions, though. *)
91 | ELet (_, _, eBind, eBody) => eff eBind orelse
92 effectful doPrint effs inFunction (bound+1) eBody
93 | EClosure (_, es) => List.exists eff es
94 (* TODO: deal with EQuery. *)
95 | EQuery _ => tru "query"
96 | EDml _ => tru "dml"
97 | ENextval _ => tru "nextval"
98 | ESetval _ => tru "setval"
99 | EUnurlify (e, _, _) => eff e
100 (* ASK: how should we treat this? *)
101 | EJavaScript _ => tru "javascript"
102 (* ASK: these are all effectful, right? *)
103 | ESignalReturn _ => tru "signalreturn"
104 | ESignalBind _ => tru "signalbind"
105 | ESignalSource _ => tru "signalsource"
106 | EServerCall _ => tru "servercall"
107 | ERecv _ => tru "recv"
108 | ESleep _ => tru "sleep"
109 | ESpawn _ => tru "spawn"
110 and eff = fn (e', _) => eff' e'
111 in
112 eff
113 end
114
115 (* TODO: test this. *)
116 val effectfulMap =
117 let
118 fun doVal ((_, name, _, e, _), effMap) =
119 if effectful false effMap false 0 e
120 then IS.add (effMap, name)
121 else effMap
122 val doDecl =
123 fn (DVal v, effMap) => doVal (v, effMap)
124 (* Repeat the list of declarations a number of times equal to its size. *)
125 | (DValRec vs, effMap) =>
126 List.foldl doVal effMap (List.concat (List.map (fn _ => vs) vs))
127 (* ASK: any other cases? *)
128 | (_, effMap) => effMap
129 in
130 MonoUtil.File.fold {typ = #2, exp = #2, decl = doDecl} IS.empty
131 end
132
133
134 (* SQL analysis. *)
19 135
20 val useInjIfPossible = 136 val useInjIfPossible =
21 fn SqConst prim => Inj (EPrim (Prim.String (Prim.Normal, Prim.toString prim)), ErrorMsg.dummySpan) 137 fn SqConst prim => Inj (EPrim (Prim.String (Prim.Normal, Prim.toString prim)),
138 ErrorMsg.dummySpan)
22 | sqexp => sqexp 139 | sqexp => sqexp
23 140
24 fun equalities (canonicalTable : string -> string) : 141 fun equalities (canonicalTable : string -> string) :
25 sqexp -> ((string * string) * Mono.exp) list option = 142 sqexp -> ((string * string) * Mono.exp) list option =
26 let 143 let
87 | Update (tab, _, _) => tab 204 | Update (tab, _, _) => tab
88 205
89 206
90 (* Program instrumentation. *) 207 (* Program instrumentation. *)
91 208
209 fun stringExp s = (EPrim (Prim.String (Prim.Normal, s)), ErrorMsg.dummySpan)
92 val stringTyp = (TFfi ("Basis", "string"), ErrorMsg.dummySpan) 210 val stringTyp = (TFfi ("Basis", "string"), ErrorMsg.dummySpan)
93 211
94 val sequence = 212 val sequence =
95 fn (exp :: exps) => 213 fn (exp :: exps) =>
96 let 214 let
101 | _ => raise Match 219 | _ => raise Match
102 220
103 fun ffiAppCache' (func, index, args) : Mono.exp' = 221 fun ffiAppCache' (func, index, args) : Mono.exp' =
104 EFfiApp ("Sqlcache", func ^ Int.toString index, args) 222 EFfiApp ("Sqlcache", func ^ Int.toString index, args)
105 223
106 fun ffiAppCache (func, index, args) : Mono. exp = 224 fun ffiAppCache (func, index, args) : Mono.exp =
107 (ffiAppCache' (func, index, args), ErrorMsg.dummySpan) 225 (ffiAppCache' (func, index, args), ErrorMsg.dummySpan)
108 226
109 val varPrefix = "queryResult" 227 val varPrefix = "queryResult"
110 228
111 fun indexOfName varName = 229 fun indexOfName varName =
112 if String.isPrefix varPrefix varName 230 if String.isPrefix varPrefix varName
113 then Int.fromString (String.extract (varName, String.size varPrefix, NONE)) 231 then Int.fromString (String.extract (varName, String.size varPrefix, NONE))
114 else NONE 232 else NONE
115 233
116 val incRels = MonoUtil.Exp.map {typ = fn x => x, exp = fn ERel n => ERel (n + 1) | x => x} 234 (* Always increments negative indices because that's what we need later. *)
235 fun incRelsBound bound inc =
236 MonoUtil.Exp.mapB
237 {typ = fn x => x,
238 exp = fn level =>
239 (fn ERel n => ERel (if n >= level orelse n < 0 then n + inc else n)
240 | x => x),
241 bind = fn (level, MonoUtil.Exp.RelE _) => level + 1 | (level, _) => level}
242 bound
243
244 val incRels = incRelsBound 0
117 245
118 (* Filled in by instrumentQuery during Monoize, used during Sqlcache. *) 246 (* Filled in by instrumentQuery during Monoize, used during Sqlcache. *)
119 val urlifiedRel0s : Mono.exp IM.map ref = ref IM.empty 247 val urlifiedRel0s : Mono.exp IM.map ref = ref IM.empty
120 248
121 (* Used by Monoize. *) 249 (* Used by Monoize. *)
127 (EQuery {state = typ, ...}, loc) => 255 (EQuery {state = typ, ...}, loc) =>
128 let 256 let
129 val i = !nextQuery before nextQuery := !nextQuery + 1 257 val i = !nextQuery before nextQuery := !nextQuery + 1
130 in 258 in
131 urlifiedRel0s := IM.insert (!urlifiedRel0s, i, urlifiedRel0); 259 urlifiedRel0s := IM.insert (!urlifiedRel0s, i, urlifiedRel0);
132 (* ASK: name variables properly? *)
133 (ELet (varPrefix ^ Int.toString i, typ, query, 260 (ELet (varPrefix ^ Int.toString i, typ, query,
134 (* Uses a dummy FFI call to keep the urlified expression around, which 261 (* Uses a dummy FFI call to keep the urlified expression around, which
135 in turn keeps the declarations required for urlification safe from 262 in turn keeps the declarations required for urlification safe from
136 MonoShake. The dummy call is removed during Sqlcache. *) 263 MonoShake. The dummy call is removed during Sqlcache. *)
137 (* ASK: is there a better way? *) 264 (* TODO: thread a Monoize.Fm.t through this module. *)
138 (ESeq ((EFfiApp ("Sqlcache", "dummy", [(urlifiedRel0, stringTyp)]), loc), 265 (ESeq ((EFfiApp ("Sqlcache", "dummy", [(urlifiedRel0, stringTyp)]), loc),
139 (ERel 0, loc)), 266 (ERel 0, loc)),
140 loc)), 267 loc)),
141 loc) 268 loc)
142 end 269 end
143 | _ => raise Match 270 | _ => raise Match
144 in 271 in
145 iq 272 iq
146 end 273 end
147 274
148 val gunk : ((string * string) * Mono.exp) list list ref = ref [[]]
149
150 fun cacheWrap (query, i, urlifiedRel0, eqs) = 275 fun cacheWrap (query, i, urlifiedRel0, eqs) =
151 case query of 276 case query of
152 (EQuery {state = typ, ...}, _) => 277 (EQuery {state = typ, ...}, _) =>
153 let 278 let
279 val () = ffiInfo := {index = i, params = length eqs} :: !ffiInfo
154 val loc = ErrorMsg.dummySpan 280 val loc = ErrorMsg.dummySpan
155 (* TODO: deal with effectful injected expressions. *) 281 (* We ensure before this step that all arguments aren't effectful.
156 val args = (ffiInfo := {index = i, params = length eqs} :: !ffiInfo; 282 by turning them into local variables as needed. *)
157 map (fn (_, e) => (e, stringTyp)) eqs) before gunk := eqs :: !gunk 283 val args = map (fn (_, e) => (e, stringTyp)) eqs
158 val argsInc = map (fn (e, t) => (incRels e, t)) args 284 val argsInc = map (fn (e, typ) => (incRels 1 e, typ)) args
285 val check = ffiAppCache ("check", i, args)
286 val store = ffiAppCache ("store", i, (urlifiedRel0, stringTyp) :: argsInc)
287 val rel0 = (ERel 0, loc)
159 in 288 in
160 (ECase (ffiAppCache ("check", i, args), 289 (ECase (check,
161 [((PNone stringTyp, loc), 290 [((PNone stringTyp, loc),
162 (ELet ("q", typ, query, 291 (ELet ("q", typ, query, (ESeq (store, rel0), loc)), loc)),
163 (ESeq (ffiAppCache ("store", i, (urlifiedRel0, stringTyp) :: argsInc),
164 (ERel 0, loc)),
165 loc)),
166 loc)),
167 ((PSome (stringTyp, (PVar ("hit", stringTyp), loc)), loc), 292 ((PSome (stringTyp, (PVar ("hit", stringTyp), loc)), loc),
168 (* ASK: what does this bool do? *) 293 (* Boolean is false because we're not unurlifying from a cookie. *)
169 (EUnurlify ((ERel 0, loc), typ, false), loc))], 294 (EUnurlify (rel0, typ, false), loc))],
170 {disc = stringTyp, result = typ}), 295 {disc = stringTyp, result = typ}),
171 loc) 296 loc)
172 end 297 end
173 | _ => raise Match 298 | _ => raise Match
174 299
179 Search.Continue x => x 304 Search.Continue x => x
180 | Search.Return _ => raise Match 305 | Search.Return _ => raise Match
181 306
182 fun fileMap doExp file = #1 (fileMapfold (fn x => fn _ => (doExp x, ())) file ()) 307 fun fileMap doExp file = #1 (fileMapfold (fn x => fn _ => (doExp x, ())) file ())
183 308
184 val addChecking = 309 fun addChecking file =
185 let 310 let
186 fun doExp queryInfo = 311 fun doExp queryInfo =
187 fn e' as ELet (v, t, queryExp as (EQuery {query = queryText, ...}, _), body) => 312 fn e' as ELet (v, t,
313 queryExp' as (EQuery {query = origQueryText,
314 initial, body, state, tables, exps}, queryLoc),
315 letBody) =>
188 let 316 let
317 val loc = ErrorMsg.dummySpan
318 val chunks = chunkify origQueryText
319 fun strcat (e1, e2) = (EStrcat (e1, e2), loc)
320 val (newQueryText, newVariables) =
321 (* Important that this is foldr (to oppose foldl below). *)
322 List.foldr
323 (fn (chunk, (qText, newVars)) =>
324 case chunk of
325 Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars)
326 | Exp (e as (ERel _, _)) => (strcat (e, qText), newVars)
327 | Exp (e as (ENamed _, _)) => (strcat (e, qText), newVars)
328 (* Head of newVars has lowest index. *)
329 | Exp e =>
330 let
331 val n = length newVars
332 in
333 (* This is the (n + 1)th new variable, so
334 there are already n new variables bound,
335 so we increment indices by n. *)
336 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars)
337 end
338 | String s => (strcat (stringExp s, qText), newVars))
339 (stringExp "", [])
340 chunks
341 fun wrapLets e' =
342 (* Important that this is foldl (to oppose foldr above). *)
343 List.foldl (fn (v, e') => ELet ("sqlArgz", stringTyp, v, (e', loc))) e' newVariables
344 (* Increment once for each new variable just made. *)
345 val queryExp = incRels (length newVariables)
346 (EQuery {query = newQueryText,
347 initial = initial,
348 body = body,
349 state = state,
350 tables = tables,
351 exps = exps},
352 queryLoc)
353 val (EQuery {query = queryText, ...}, _) = queryExp
354 (* val () = Print.preface ("sqlcache> ", (MonoPrint.p_exp MonoEnv.empty queryText)); *)
189 fun bind x f = Option.mapPartial f x 355 fun bind x f = Option.mapPartial f x
356 fun guard b x = if b then x else NONE
357 (* DEBUG: set first boolean argument to true to turn on printing. *)
358 fun safe bound = not o effectful true (effectfulMap file) false bound
190 val attempt = 359 val attempt =
191 (* Ziv misses Haskell's do notation.... *) 360 (* Ziv misses Haskell's do notation.... *)
361 guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) (
192 bind (parse query queryText) (fn queryParsed => 362 bind (parse query queryText) (fn queryParsed =>
193 (Print.preface ("gunk> ", (MonoPrint.p_exp MonoEnv.empty queryExp));
194 bind (indexOfName v) (fn i => 363 bind (indexOfName v) (fn i =>
195 bind (equalitiesQuery queryParsed) (fn eqs => 364 bind (equalitiesQuery queryParsed) (fn eqs =>
196 bind (IM.find (!urlifiedRel0s, i)) (fn urlifiedRel0 => 365 bind (IM.find (!urlifiedRel0s, i)) (fn urlifiedRel0 =>
197 SOME (ELet (v, t, cacheWrap (queryExp, i, urlifiedRel0, eqs), body), 366 SOME (wrapLets (ELet (v, t,
367 cacheWrap (queryExp, i, urlifiedRel0, eqs),
368 incRelsBound 1 (length newVariables) letBody)),
198 SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, i)) 369 SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, i))
199 queryInfo 370 queryInfo
200 (tablesQuery queryParsed))))))) 371 (tablesQuery queryParsed)))))))
201 in 372 in
202 case attempt of 373 case attempt of
204 | NONE => (e', queryInfo) 375 | NONE => (e', queryInfo)
205 end 376 end
206 | ESeq ((EFfiApp ("Sqlcache", "dummy", _), _), (e', _)) => (e', queryInfo) 377 | ESeq ((EFfiApp ("Sqlcache", "dummy", _), _), (e', _)) => (e', queryInfo)
207 | e' => (e', queryInfo) 378 | e' => (e', queryInfo)
208 in 379 in
209 fn file => fileMapfold (fn exp => fn state => doExp state exp) file SIMM.empty 380 fileMapfold (fn exp => fn state => doExp state exp) file SIMM.empty
210 end 381 end
211 382
212 fun addFlushing (file, queryInfo) = 383 fun addFlushing (file, queryInfo) =
213 let 384 let
214 val allIndices : int list = SM.foldr (fn (x, acc) => IS.listItems x @ acc) [] queryInfo 385 val allIndices : int list = SM.foldr (fn (x, acc) => IS.listItems x @ acc) [] queryInfo
229 end 400 end
230 401
231 fun go file = 402 fun go file =
232 let 403 let
233 val () = Sql.sqlcacheMode := true 404 val () = Sql.sqlcacheMode := true
234 in 405 val file' = addFlushing (addChecking file)
235 addFlushing (addChecking file) before Sql.sqlcacheMode := false 406 val () = Sql.sqlcacheMode := false
407 in
408 file'
236 end 409 end
237 410
238 411
239 (* BEGIN OLD 412 (* BEGIN OLD
240 413