comparison src/sqlcache.sml @ 2266:afd12c75e0d6

Do SQL and pure caching in the same pass.
author Ziv Scully <ziv@mit.edu>
date Wed, 14 Oct 2015 15:45:04 -0400
parents a647a1560628
children e5b7b066bf1b
comparison
equal deleted inserted replaced
2265:a647a1560628 2266:afd12c75e0d6
54 val doBind = 54 val doBind =
55 fn (env, MonoUtil.Exp.RelE (x, t)) => MonoEnv.pushERel env x t NONE 55 fn (env, MonoUtil.Exp.RelE (x, t)) => MonoEnv.pushERel env x t NONE
56 | (env, MonoUtil.Exp.NamedE (x, n, t, eo, s)) => MonoEnv.pushENamed env x n t eo s 56 | (env, MonoUtil.Exp.NamedE (x, n, t, eo, s)) => MonoEnv.pushENamed env x n t eo s
57 | (env, MonoUtil.Exp.Datatype (x, n, cs)) => MonoEnv.pushDatatype env x n cs 57 | (env, MonoUtil.Exp.Datatype (x, n, cs)) => MonoEnv.pushDatatype env x n cs
58 58
59 (***********************)
60 (* General Combinators *)
61 (***********************)
62
63 (* From the MLton wiki. *)
64 infix 3 <\ fun x <\ f = fn y => f (x, y) (* Left section *)
65 infix 3 \> fun f \> y = f y (* Left application *)
66 infixr 3 /> fun f /> y = fn x => f (x, y) (* Right section *)
67 infixr 3 </ fun x </ f = f x (* Right application *)
68
69 (* Option monad. *)
70 fun obind (x, f) = Option.mapPartial f x
71 fun oguard (b, x) = if b then x else NONE
59 72
60 (*******************) 73 (*******************)
61 (* Effect Analysis *) 74 (* Effect Analysis *)
62 (*******************) 75 (*******************)
63 76
540 Search.Continue x => x 553 Search.Continue x => x
541 | Search.Return _ => raise Match 554 | Search.Return _ => raise Match
542 555
543 fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ()) 556 fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ())
544 557
558 (* Takes a text expression and returns
559 newText: a new expression with any subexpressions that do computation
560 replaced with variables,
561 wrapLets: a function that wraps its argument expression with lets binding
562 those variables to their corresponding computations, and
563 numArgs: the number of such bindings.
564 The De Bruijn indices work out for [wrapLets (incRels numArgs newText)], but
565 the intention is that newText might be augmented. *)
566 fun factorOutNontrivial text =
567 let
568 val loc = dummyLoc
569 fun strcat (e1, e2) = (EStrcat (e1, e2), loc)
570 val chunks = Sql.chunkify text
571 val (newText, newVariables) =
572 (* Important that this is foldr (to oppose foldl below). *)
573 List.foldr
574 (fn (chunk, (qText, newVars)) =>
575 (* Variable bound to the head of newVars will have the lowest index. *)
576 case chunk of
577 (* EPrim should always be a string in this case. *)
578 Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars)
579 | Sql.Exp e =>
580 let
581 val n = length newVars
582 in
583 (* This is the (n+1)th new variable, so there are
584 already n new variables bound, so we increment
585 indices by n. *)
586 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars)
587 end
588 | Sql.String s => (strcat (stringExp s, qText), newVars))
589 (stringExp "", [])
590 chunks
591 fun wrapLets e' =
592 (* Important that this is foldl (to oppose foldr above). *)
593 List.foldl (fn (v, e') => ELet (varName "sqlArg", stringTyp, v, (e', loc)))
594 e'
595 newVariables
596 val numArgs = length newVariables
597 in
598 (newText, wrapLets, numArgs)
599 end
600
545 601
546 (**********************) 602 (**********************)
547 (* Mono Type Checking *) 603 (* Mono Type Checking *)
548 (**********************) 604 (**********************)
549 605
597 | _ => NONE 653 | _ => NONE
598 654
599 and typOfExp env (e', loc) = typOfExp' env e' 655 and typOfExp env (e', loc) = typOfExp' env e'
600 656
601 657
602 (*******************************) 658 (***********)
603 (* Caching Pure Subexpressions *) 659 (* Caching *)
604 (*******************************) 660 (***********)
605 661
606 fun cacheWrap (env, exp, resultTyp, args, i) = 662 fun cacheWrap (env, exp, resultTyp, args, i) =
607 let 663 let
608 val loc = dummyLoc 664 val loc = dummyLoc
609 val rel0 = (ERel 0, loc) 665 val rel0 = (ERel 0, loc)
642 0 698 0
643 IS.empty 699 IS.empty
644 700
645 val expSize = MonoUtil.Exp.fold {typ = #2, exp = fn (_, n) => n+1} 0 701 val expSize = MonoUtil.Exp.fold {typ = #2, exp = fn (_, n) => n+1} 0
646 702
647 structure InvalidationInfo :> sig
648 type t
649 val empty : t
650 val fromList : int list -> t
651 val toList : t -> int list
652 val union : t * t -> t
653 val unbind : t * int -> t option
654 end = struct
655
656 (* Keep track of the minimum explicitly. NONE is the empty set. *)
657 type t = (int * IS.set) option
658
659 val fromList =
660 List.foldl
661 (fn (n, NONE) => SOME (n, IS.singleton n)
662 | (n', SOME (n, ns)) => SOME (Int.min (n, n'), IS.add (ns, n')))
663 NONE
664
665 val empty = fromList []
666
667 val toList =
668 fn NONE => []
669 | SOME (_, ns) => IS.listItems ns
670
671 val union =
672 fn (SOME (n1, ns1), SOME (n2, ns2)) => SOME (Int.min (n1, n2), IS.union (ns1, ns2))
673 | (NONE, info) => info
674 | (info, NONE) => info
675
676 val unbind =
677 fn (SOME (n, ns), unbound) =>
678 let
679 val n = n - unbound
680 in
681 if n < 0
682 then NONE
683 else SOME (SOME (n, IS.map (fn n => n - unbound) ns))
684 end
685 | _ => SOME NONE
686
687 end
688
689 val unionUnbind =
690 List.foldl
691 (fn (_, NONE) => NONE
692 | ((info, unbound), SOME infoAcc) =>
693 case InvalidationInfo.unbind (info, unbound) of
694 NONE => NONE
695 | SOME info => SOME (InvalidationInfo.union (info, infoAcc)))
696 (SOME InvalidationInfo.empty)
697
698 datatype subexp = Pure of unit -> exp | Impure of exp 703 datatype subexp = Pure of unit -> exp | Impure of exp
699 704
700 val isImpure = 705 val isImpure =
701 fn Pure _ => false 706 fn Pure _ => false
702 | Impure _ => true 707 | Impure _ => true
706 | Impure e => e 711 | Impure e => e
707 712
708 (* TODO: pick a number. *) 713 (* TODO: pick a number. *)
709 val sizeWorthCaching = 5 714 val sizeWorthCaching = 5
710 715
711 fun makeCache (env, exp', index) = 716 type state = (SIMM.multimap * (Sql.query * int) IntBinaryMap.map * int)
717
718 fun incIndex (x, y, index) = (x, y, index+1)
719
720 fun cacheQuery effs env (state as (tableToIndices, indexToQueryNumArgs, index)) =
721 fn q as {query = origQueryText,
722 state = resultTyp,
723 initial, body, tables, exps} =>
724 let
725 val (newQueryText, wrapLets, numArgs) = factorOutNontrivial origQueryText
726 (* Increment once for each new variable just made. This is where we
727 use the negative De Bruijn indices hack. *)
728 (* TODO: please don't use that hack. As anyone could have predicted, it
729 was incomprehensible a year later.... *)
730 val queryExp = incRels numArgs
731 (EQuery {query = newQueryText,
732 state = resultTyp,
733 initial = initial,
734 body = body,
735 tables = tables,
736 exps = exps},
737 dummyLoc)
738 (* DEBUG *)
739 (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *)
740 val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc))
741 (* We use dummyTyp here. I think this is okay because databases don't
742 store (effectful) functions, but perhaps there's some pathalogical
743 corner case missing.... *)
744 fun safe bound =
745 not
746 o effectful effs
747 (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE)
748 bound
749 env)
750 val textOfQuery = fn (EQuery {query, ...}, _) => SOME query | _ => NONE
751 val attempt =
752 (* Ziv misses Haskell's do notation.... *)
753 textOfQuery queryExp
754 <\obind\>
755 (fn queryText =>
756 (safe 0 queryText andalso safe 0 initial andalso safe 2 body)
757 <\oguard\>
758 Sql.parse Sql.query queryText
759 <\obind\>
760 (fn queryParsed =>
761 (cacheWrap (env, queryExp, resultTyp, args, index))
762 <\obind\>
763 (fn cachedExp =>
764 SOME (wrapLets cachedExp,
765 (SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, index))
766 tableToIndices
767 (tablesQuery queryParsed),
768 IM.insert (indexToQueryNumArgs, index, (queryParsed, numArgs)),
769 index + 1)))))
770 in
771 case attempt of
772 SOME pair => pair
773 (* Even in this case, we have to increment index to avoid some bug,
774 but I forget exactly what it is or why this helps. *)
775 (* TODO: just use a reference for current index.... *)
776 | NONE => (EQuery q, incIndex state)
777 end
778
779 fun cachePure (env, exp', (_, _, index)) =
712 case typOfExp' env exp' of 780 case typOfExp' env exp' of
713 NONE => NONE 781 NONE => NONE
714 | SOME (TFun _, _) => NONE 782 | SOME (TFun _, _) => NONE
715 | SOME typ => 783 | SOME typ =>
716 if expSize (exp', dummyLoc) < sizeWorthCaching 784 (expSize (exp', dummyLoc) < sizeWorthCaching)
717 then NONE 785 </oguard/>
718 else case List.foldr (fn ((_, _), NONE) => NONE 786 (List.foldr (fn (_, NONE) => NONE
719 | ((n, typ), SOME args) => 787 | ((n, typ), SOME args) =>
720 case MonoFooify.urlify env ((ERel n, dummyLoc), typ) of 788 (MonoFooify.urlify env ((ERel n, dummyLoc), typ))
721 NONE => NONE 789 </obind/>
722 | SOME arg => SOME (arg :: args)) 790 (fn arg => SOME (arg :: args)))
723 (SOME []) 791 (SOME [])
724 (map (fn n => (n, #2 (MonoEnv.lookupERel env n))) 792 (map (fn n => (n, #2 (MonoEnv.lookupERel env n)))
725 (freeVars (exp', dummyLoc))) of 793 (freeVars (exp', dummyLoc))))
726 NONE => NONE 794 </obind/>
727 | SOME args => cacheWrap (env, (exp', dummyLoc), typ, args, index) 795 (fn args => cacheWrap (env, (exp', dummyLoc), typ, args, index))
728 796
729 fun pureCache (effs : IS.set) ((env, exp as (exp', loc)), index) : subexp * int = 797 fun cache (effs : IS.set) ((env, exp as (exp', loc)), state) : subexp * state =
730 let 798 let
731 fun wrapBindN f (args : (MonoEnv.env * exp) list) = 799 fun wrapBindN f (args : (MonoEnv.env * exp) list) =
732 let 800 let
733 val (subexps, index) = ListUtil.foldlMap (pureCache effs) index args 801 val (subexps, state) = ListUtil.foldlMap (cache effs) state args
734 fun mkExp () = (f (map expOfSubexp subexps), loc) 802 fun mkExp () = (f (map expOfSubexp subexps), loc)
735 in 803 in
736 if List.exists isImpure subexps 804 if List.exists isImpure subexps
737 then (Impure (mkExp ()), index) 805 then (Impure (mkExp ()), state)
738 else (Pure (fn () => case makeCache (env, f (map #2 args), index) of 806 else (Pure (fn () => case cachePure (env, f (map #2 args), state) of
739 NONE => mkExp () 807 NONE => mkExp ()
740 | SOME e' => (e', loc)), 808 | SOME e' => (e', loc)),
741 (* Conservatively increment index. *) 809 (* Conservatively increment index. *)
742 index + 1) 810 incIndex state)
743 end 811 end
744 fun wrapBind1 f arg = 812 fun wrapBind1 f arg =
745 wrapBindN (fn [arg] => f arg | _ => raise Match) [arg] 813 wrapBindN (fn [arg] => f arg | _ => raise Match) [arg]
746 fun wrapBind2 f (arg1, arg2) = 814 fun wrapBind2 f (arg1, arg2) =
747 wrapBindN (fn [arg1, arg2] => f (arg1, arg2) | _ => raise Match) [arg1, arg2] 815 wrapBindN (fn [arg1, arg2] => f (arg1, arg2) | _ => raise Match) [arg1, arg2]
752 case exp' of 820 case exp' of
753 ECon (dk, pc, SOME e) => wrap1 (fn e => ECon (dk, pc, SOME e)) e 821 ECon (dk, pc, SOME e) => wrap1 (fn e => ECon (dk, pc, SOME e)) e
754 | ESome (t, e) => wrap1 (fn e => ESome (t, e)) e 822 | ESome (t, e) => wrap1 (fn e => ESome (t, e)) e
755 | EFfiApp (s1, s2, args) => 823 | EFfiApp (s1, s2, args) =>
756 if ffiEffectful (s1, s2) 824 if ffiEffectful (s1, s2)
757 then (Impure exp, index) 825 then (Impure exp, state)
758 else wrapN (fn es => 826 else wrapN (fn es =>
759 EFfiApp (s1, s2, ListPair.map (fn (e, (_, t)) => (e, t)) (es, args))) 827 EFfiApp (s1, s2, ListPair.map (fn (e, (_, t)) => (e, t)) (es, args)))
760 (map #1 args) 828 (map #1 args)
761 | EApp (e1, e2) => wrap2 EApp (e1, e2) 829 | EApp (e1, e2) => wrap2 EApp (e1, e2)
762 | EAbs (s, t1, t2, e) => 830 | EAbs (s, t1, t2, e) =>
782 | ELet (s, t, e1, e2) => 850 | ELet (s, t, e1, e2) =>
783 wrapBind2 (fn (e1, e2) => ELet (s, t, e1, e2)) 851 wrapBind2 (fn (e1, e2) => ELet (s, t, e1, e2))
784 ((env, e1), (MonoEnv.pushERel env s t (SOME e1), e2)) 852 ((env, e1), (MonoEnv.pushERel env s t (SOME e1), e2))
785 (* ASK: | EClosure (n, es) => ? *) 853 (* ASK: | EClosure (n, es) => ? *)
786 | EUnurlify (e, t, b) => wrap1 (fn e => EUnurlify (e, t, b)) e 854 | EUnurlify (e, t, b) => wrap1 (fn e => EUnurlify (e, t, b)) e
855 | EQuery q =>
856 let
857 val (exp', state) = cacheQuery effs env state q
858 in
859 (Impure (exp', loc), state)
860 end
787 | _ => if effectful effs env exp 861 | _ => if effectful effs env exp
788 then (Impure exp, index) 862 then (Impure exp, state)
789 else (Pure (fn () => (case makeCache (env, exp', index) of 863 else (Pure (fn () => (case cachePure (env, exp', state) of
790 NONE => exp' 864 NONE => exp'
791 | SOME e' => e', 865 | SOME e' => e',
792 loc)), 866 loc)),
793 index + 1) 867 incIndex state)
794 end 868 end
795 869
796 fun addPure (file, indexStart, effs) = 870 fun addCaching file =
797 let 871 let
798 fun doTopLevelExp env exp index = 872 val effs = effectfulDecls file
873 fun doTopLevelExp env exp state =
799 let 874 let
800 val (subexp, index) = pureCache effs ((env, exp), index) 875 val (subexp, state) = cache effs ((env, exp), state)
801 in 876 in
802 (expOfSubexp subexp, index) 877 (expOfSubexp subexp, state)
803 end 878 end
804 in 879 in
805 #1 (fileTopLevelMapfoldB doTopLevelExp file indexStart) 880 ((fileTopLevelMapfoldB doTopLevelExp file (SIMM.empty, IM.empty, 0)), effs)
806 end
807
808
809 (***********************)
810 (* Caching SQL Queries *)
811 (***********************)
812
813 fun factorOutNontrivial text =
814 let
815 val loc = dummyLoc
816 fun strcat (e1, e2) = (EStrcat (e1, e2), loc)
817 val chunks = Sql.chunkify text
818 val (newText, newVariables) =
819 (* Important that this is foldr (to oppose foldl below). *)
820 List.foldr
821 (fn (chunk, (qText, newVars)) =>
822 (* Variable bound to the head of newBs will have the lowest index. *)
823 case chunk of
824 Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars)
825 | Sql.Exp e =>
826 let
827 val n = length newVars
828 in
829 (* This is the (n+1)th new variable, so there are
830 already n new variables bound, so we increment
831 indices by n. *)
832 (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars)
833 end
834 | Sql.String s => (strcat (stringExp s, qText), newVars))
835 (stringExp "", [])
836 chunks
837 fun wrapLets e' =
838 (* Important that this is foldl (to oppose foldr above). *)
839 List.foldl (fn (v, e') => ELet (varName "sqlArg", stringTyp, v, (e', loc)))
840 e'
841 newVariables
842 val numArgs = length newVariables
843 in
844 (newText, wrapLets, numArgs)
845 end
846
847 fun cacheQuery effs env (queryInfo as (tableToIndices, indexToQueryNumArgs, index)) =
848 fn e' as EQuery {query = origQueryText,
849 state = resultTyp,
850 initial, body, tables, exps} =>
851 let
852 val (newQueryText, wrapLets, numArgs) = factorOutNontrivial origQueryText
853 (* Increment once for each new variable just made. *)
854 val queryExp = incRels numArgs
855 (EQuery {query = newQueryText,
856 state = resultTyp,
857 initial = initial,
858 body = body,
859 tables = tables,
860 exps = exps},
861 dummyLoc)
862 (* DEBUG *)
863 (* val () = Print.preface ("sqlcache> ", MonoPrint.p_exp MonoEnv.empty queryText) *)
864 val args = List.tabulate (numArgs, fn n => (ERel n, dummyLoc))
865 fun bind x f = Option.mapPartial f x
866 fun guard b x = if b then x else NONE
867 (* We use dummyTyp here. I think this is okay because databases don't
868 store (effectful) functions, but perhaps there's some pathalogical
869 corner case missing.... *)
870 fun safe bound =
871 not
872 o effectful effs
873 (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE)
874 bound
875 env)
876 val textOfQuery = fn (EQuery {query, ...}, _) => SOME query | _ => NONE
877 val attempt =
878 (* Ziv misses Haskell's do notation.... *)
879 bind (textOfQuery queryExp) (fn queryText =>
880 guard (safe 0 queryText andalso safe 0 initial andalso safe 2 body) (
881 bind (Sql.parse Sql.query queryText) (fn queryParsed =>
882 bind (cacheWrap (env, queryExp, resultTyp, args, index)) (fn cachedExp =>
883 SOME (wrapLets cachedExp,
884 (SS.foldr (fn (tab, qi) => SIMM.insert (qi, tab, index))
885 tableToIndices
886 (tablesQuery queryParsed),
887 IM.insert (indexToQueryNumArgs, index, (queryParsed, numArgs)),
888 index + 1))))))
889 in
890 case attempt of
891 SOME pair => pair
892 (* We have to increment index conservatively. *)
893 (* TODO: just use a reference for current index.... *)
894 | NONE => (e', (tableToIndices, indexToQueryNumArgs, index + 1))
895 end
896 | e' => (e', queryInfo)
897
898 fun addChecking file =
899 let
900 val effs = effectfulDecls file
901 in
902 (fileAllMapfoldB (fn env => fn exp => fn state => cacheQuery effs env state exp)
903 file
904 (SIMM.empty, IM.empty, 0),
905 effs)
906 end 881 end
907 882
908 883
909 (************) 884 (************)
910 (* Flushing *) 885 (* Flushing *)
993 end 968 end
994 | e' => e' 969 | e' => e'
995 in 970 in
996 (* DEBUG *) 971 (* DEBUG *)
997 (* gunk := []; *) 972 (* gunk := []; *)
998 (fileMap doExp file, index, effs) 973 fileMap doExp file
999 end 974 end
1000 975
1001 976
1002 (***************) 977 (***************)
1003 (* Entry point *) 978 (* Entry point *)
1024 val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls 999 val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls
1025 in 1000 in
1026 (datatypes @ newDecls @ others, sideInfo) 1001 (datatypes @ newDecls @ others, sideInfo)
1027 end 1002 end
1028 1003
1029 val go' = addPure o addFlushing o addChecking o inlineSql 1004 val go' = addFlushing o addCaching o inlineSql
1030 1005
1031 fun go file = 1006 fun go file =
1032 let 1007 let
1033 (* TODO: do something nicer than [Sql] being in one of two modes. *) 1008 (* TODO: do something nicer than [Sql] being in one of two modes. *)
1034 val () = (resetFfiInfo (); Sql.sqlcacheMode := true) 1009 val () = (resetFfiInfo (); Sql.sqlcacheMode := true)