Home My Page Projects Code Snippets Project Openings SML/NJ
Summary Activity Forums Tracker Lists Tasks Docs Surveys News SCM Files

SCM Repository

[smlnj] Diff of /sml/trunk/src/compiler/FLINT/opt/lcontract.sml
ViewVC logotype

Diff of /sml/trunk/src/compiler/FLINT/opt/lcontract.sml

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 23, Thu Mar 12 00:49:56 1998 UTC revision 24, Thu Mar 12 00:49:58 1998 UTC
# Line 3  Line 3 
3    
4  signature LCONTRACT =  signature LCONTRACT =
5  sig  sig
6    val lcontract : FLINT.prog -> FLINT.prog    val lcontract : Lambda.lexp -> Lambda.lexp
7  end  end
8    
9  structure LContract : LCONTRACT =  structure LContract : LCONTRACT =
10  struct  struct
11    
12  local structure DI = DebIndex  local structure DI = DebIndex
13        structure DA = Access        open Access Lambda
       structure LT = LtyExtern  
       open FLINT  
14  in  in
15    
16  fun bug s = ErrorMsg.impossible ("LContract: "^s)  val sameName = LambdaVar.sameName
17  val say = Control.Print.say  fun bug s = ErrorMsg.impossible ("LambdaOpt: "^s)
18  val ident = fn x => x  val ident = fn le => le
19  fun all p (a::r) = p a andalso all p r | all p nil = true  fun all p (a::r) = p a andalso all p r | all p nil = true
20    
21  fun isDiffs (vs, us) =  fun isDiff(x, VAR v) = (x <> v)
22    let fun h (VAR x) = List.all (fn y => (y<>x)) vs    | isDiff(x, GENOP({default,table}, _, _, _)) =
23          | h _ = true        (x <> default) andalso (all (fn (_, w) => (x <> w)) table)
24     in List.all h us    | isDiff _ = true
   end  
   
 fun isEqs (vs, us) =  
   let fun h (v::r, (VAR x)::z) = if v = x then h(r, z) else false  
         | h ([], []) = true  
         | h _ = false  
    in h(vs, us)  
   end  
25    
26  datatype info  datatype info
27    = SimpVal of value    = CompExp
28      | SimpVal of value
29    | ListExp of value list    | ListExp of value list
30    | FunExp of DI.depth * lvar list * lexp    | FunExp of lvar * lty * lexp
31    | ConExp of dcon * tyc list * value    | SimpExp
32    | StdExp  
33    fun isPure(SVAL _) = true
34      | isPure(RECORD _) = true
35      | isPure(SRECORD _) = true
36      | isPure(VECTOR _) = true
37      | isPure(SELECT _) = true
38      | isPure(FN _) = true
39      | isPure(TFN _) = true
40      | isPure(CON _) = true
41      | isPure(DECON _) = true (* this can be problematic *)
42      | isPure(ETAG _) = true
43      | isPure(PACK _) = true
44      | isPure(WRAP _) = true
45      | isPure(UNWRAP _) = true
46      | isPure(SWITCH(v, _, ces, oe)) =
47          let fun g((_,x)::r) = if isPure x then g r else false
48                | g [] = case oe of NONE => true | SOME z => isPure z
49           in g ces
50          end
51      | isPure _ = false
52      (*** the cases for FIX and LET have already been flattened, thus
53           they should not occur ***)
54    
55  exception LContPass1  exception LContPass1
56  fun pass1 fdec =  fun pass1 lexp =
57    let val zz : (DI.depth option) Intmap.intmap = Intmap.new(32, LContPass1)    let val zz : (DI.depth option) Intmap.intmap = Intmap.new(32, LContPass1)
58        val add = Intmap.add zz        val add = Intmap.add zz
59        val get = Intmap.map zz        val get = Intmap.map zz
# Line 53  Line 65 
65               val _ = rmv x               val _ = rmv x
66            in case s            in case s
67                of NONE => ()                of NONE => ()
                | SOME _ => add(x, NONE)  (* depth no longer matters *)  
 (*  
68                 | SOME d => if (d=nd) then add(x, NONE)                 | SOME d => if (d=nd) then add(x, NONE)
69                             else ()                             else ()
 *)  
70           end) handle _ => ()           end) handle _ => ()
71    
72        fun cand x = (get x; true) handle _ => false        fun cand x = (get x; true) handle _ => false
73    
74        fun lpfd d (FK_FUN {isrec=SOME _,...}, v, vts, e) = lple d e        fun loop (e, d) =
         | lpfd d (_, v, vts, e) = (enter(v, d); lple d e)  
   
       and lple d e =  
75          let fun psv (VAR x) = kill x          let fun psv (VAR x) = kill x
76                | psv _ = ()                | psv _ = ()
77    
78              and pst (v, vks, e) = lple (DI.next d) e              and pse (SVAL v) = psv v
79                  | pse (FN(v, _, e)) = pse e
80              and pse (RET vs) = app psv vs                | pse (APP(VAR x, v2)) = (mark d x; psv v2)
81                | pse (LET(vs, e1, e2)) = (pse e1; pse e2)                | pse (APP(v1, v2)) = (psv v1; psv v2)
82                | pse (FIX(fdecs, e)) = (app (lpfd d) fdecs; pse e)                | pse (FIX(vs, ts, es, be)) = (app pse es; pse be)
83                | pse (APP(VAR x, vs)) = (mark d x; app psv vs)                | pse (LET(v, FN (_,_,e1), e2)) = (enter(v, d); pse e1; pse e2)
84                | pse (APP(v, vs)) = (psv v; app psv vs)                | pse (LET(v, e1, e2)) = (pse e1; pse e2)
85                | pse (TFN(tfdec, e)) = (pst tfdec; pse e)                | pse (TFN(ks, e)) = loop(e, DI.next d)
86                | pse (TAPP(v, _)) = psv v                | pse (TAPP(v, _)) = psv v
87                | pse (RECORD(_,vs,_,e)) = (app psv vs; pse e)                | pse (VECTOR(vs,_)) = app psv vs
88                | pse (SELECT(u,_,_,e)) = (psv u; pse e)                | pse (RECORD vs) = app psv vs
89                | pse (CON(_,_,u,_,e)) = (psv u; pse e)                | pse (SRECORD vs) = app psv vs
90                | pse (SWITCH(u, _, ces, oe)) =                | pse (SELECT(_,v)) = psv v
91                    (psv u; app (fn (_,x) => pse x) ces;                | pse (CON(_,_,v)) = psv v
92                  | pse (DECON(_,_,v)) = psv v
93                  | pse (SWITCH(v, _, ces, oe)) =
94                      (psv v; app (fn (_,x) => pse x) ces;
95                     case oe of NONE => () | SOME x => pse x)                     case oe of NONE => () | SOME x => pse x)
96                | pse (RAISE _) = ()                | pse (ETAG(v, _)) = psv v
97                | pse (HANDLE(e,v)) = (pse e; psv v)                | pse (HANDLE(e,v)) = (pse e; psv v)
98                | pse (BRANCH(_, vs, e1, e2)) = (app psv vs; pse e1; pse e2)                | pse (PACK(_,_,_,v)) = psv v
99                | pse (PRIMOP(_, vs, _, e)) = (app psv vs; pse e)                | pse (WRAP(_,_,v)) = psv v
100                  | pse (UNWRAP(_,_,v)) = psv v
101                  | pse (RAISE _) = ()
102    
103           in pse e           in pse e
104          end          end
105    
106     in lpfd DI.top fdec; (cand, fn () => Intmap.clear zz)     in loop (lexp, DI.top); cand
107    end (* pass1 *)    end
108    
109  (************************************************************************  (************************************************************************
110   *                      THE MAIN FUNCTION                               *   *                      THE MAIN FUNCTION                               *
111   ************************************************************************)   ************************************************************************)
112  fun lcontract (fdec, init) =  fun lcontract lexp =
113  let  let
114    
115  (* In pass1, we calculate the list of functions that are the candidates  val isCand = pass1 lexp
  * for contraction. To be such a candidate, a function must be called  
  * only once, and furthermore, the call site must be at the same  
  * depth as the definition site. (ZHONG)  
  *  
  * Being at the same depth is not strictly necessary, we'll relax this  
  * constraint in the future.  
  *)  
 val (isCand, cleanUp) =  
  if init then (fn _ => false, fn () => ()) else pass1 fdec  
116    
117  exception LContract  exception LContract
118  val m : (int ref * info) Intmap.intmap = Intmap.new(32, LContract)  val m : (int ref * info) Intmap.intmap = Intmap.new(32, LContract)
# Line 121  Line 123 
123    
124  fun chkIn (v, info) = enter(v, (ref 0, info))  fun chkIn (v, info) = enter(v, (ref 0, info))
125    
126  (** check if a variable is dead *)  fun refer v =
 fun dead v = (case get v of (ref 0, _) => true  
                           | _ => false) handle _ => false  
   
 (** check if all variables are dead *)  
 fun alldead [] = true  
   | alldead (v::r) = if dead v then alldead r else false  
   
 (** renaming a value *)  
 fun rename (u as (VAR v)) =  
127        ((case get v        ((case get v
128           of (_, SimpVal sv) => rename sv       of (_, SimpVal sv) => SOME sv
129            | (x, _) => (x := (!x) + 1; u)) handle _ => u)        | (x, _) => (x := (!x) + 1; NONE)) handle _ => NONE)
   | rename u = u  
130    
131  (** selecting a field from a potentially known record *)  fun selInfo v = (SOME(get v)) handle _ => NONE
132  fun selInfo (VAR v, i)  =  
133        ((case get v  fun chkOut v =
134           of (_, SimpVal u) => selInfo (u, i)    (let val x = get v
135            | (_, ListExp vs) =>      in kill v; SOME x
136       end handle _ => NONE)
137    
138    
139    fun mkInfo (_, RECORD vs) = ListExp vs
140      | mkInfo (_, SRECORD vs) = ListExp vs
141      | mkInfo (v, SELECT(i, VAR x)) =
142          let fun h z =
143                (case selInfo z
144                  of SOME(_, ListExp vs) =>
145                let val nv = List.nth(vs, i)                let val nv = List.nth(vs, i)
146                             handle _ => bug "unexpected List.Nth in selInfo"                           handle _ => bug "unexpected List.Nth in SELECT"
147                 in SOME nv                      in SimpVal nv
148                end                end
149            | _ => NONE) handle _ => NONE)                 | SOME(_, SimpVal (VAR w)) => h w
150    | selInfo _ = NONE                 | _ => SimpExp)
151            in h x
 (** applying a switch to a data constructor *)  
 fun swiInfo (VAR v, ces, oe) =  
       ((case get v  
          of (_, SimpVal u) => swiInfo(u, ces, oe)  
           | (_, ConExp (dc as (_,rep,_), ts, u)) =>  
                let fun h ((DATAcon(dc as (_,nrep,_),ts,x),e)::r) =  
                          if rep=nrep then SOME(LET([x], RET [u], e)) else h r  
                      | h (_::r) = bug "unexpected case in swiInfo"  
                      | h [] = oe  
                 in h ces  
152                 end                 end
           | _ => NONE) handle _ => NONE)  
   | swiInfo _ = NONE  
153    
154  (** contracting a function application *)    | mkInfo (v, e as FN x) = if isCand v then FunExp x else SimpExp
155  fun appInfo (VAR v) =    | mkInfo (_, e) = if isPure e then SimpExp else CompExp
       ((case get v  
          of (ref 0, FunExp (d, vs, e)) => SOME (d, vs, e)  
           | _ => NONE) handle _ => NONE)  
   | appInfo _ = NONE  
   
 fun transform [] = bug "unexpected case in transform"  
   | transform (cfg as ((d, od, k)::rcfg)) = let  
      fun h (f, t, (d, od, k)::r, sk) = h(f, f(t, od, d, k+sk), r, k+sk)  
        | h (f, t, [], _) = t  
      fun ltf t = h(LT.lt_adj_k, t, cfg, 0)  
      fun tcf t = h(LT.tc_adj_k, t, cfg, 0)  
156    
157       fun lpacc (DA.LVAR v) =  fun lpacc (LVAR v) =
158             (case lpsv (VAR v) of VAR w => DA.LVAR w        (case lpsv (VAR v)
159            of VAR w => LVAR w
160                                 | _ => bug "unexpected in lpacc")                                 | _ => bug "unexpected in lpacc")
161         | lpacc _ = bug "unexpected path in lpacc"         | lpacc _ = bug "unexpected path in lpacc"
162    
163       and lpdc (s, DA.EXN acc, t) = (s, DA.EXN(lpacc acc), ltf t)  and lpdc (s, EXN acc, t) = (s, EXN(lpacc acc), t)
164         | lpdc (s, rep, t) = (s, rep, ltf t)    | lpdc x = x
165    
166       and lpcon (DATAcon (dc, ts, v)) = DATAcon(lpdc dc, map tcf ts, v)  and lpcon (DATAcon dc) = DATAcon(lpdc dc)
167         | lpcon c = c         | lpcon c = c
168    
169       and lpdt (SOME {default=v, table=ws}) =  and lpdt {default=v, table=ws} =
170             let fun h x =    let fun h x = case (refer x)
171                   case rename (VAR x) of VAR nv => nv                   of SOME(VAR nv) => nv
172                                        | _ => bug "unexpected acse in lpdt"                    | NONE => x
173              in (SOME {default=h v, table=map (fn (ts,w) => (ts,h w)) ws})     in {default=h v, table=map (fn (ts,w) => (ts,h w)) ws}
174             end    end
175         | lpdt NONE = NONE  
176    and lpsv x =
177       and lpsv x = (case x of VAR v => rename x | _ => x)    (case x
178        of VAR v => (case (refer v) of SOME nsv => lpsv nsv
179       and lpfd (fk, v, vts, e) =                                   | NONE => (x : value))
180         (fk, v, map (fn (v,t) => (v,ltf t)) vts, #1(loop e))       | GENOP(dict, p, lt, ts) => GENOP(lpdt dict, p, lt, ts)
181         | _ => x)
      and lplet (hdr: lexp -> lexp, pure, v: lvar, info: info, e) =  
        let val _ = chkIn(v, info)  
            val (ne, b) = loop e  
         in if pure then (if dead v then (ne, b) else (hdr ne, b))  
            else (hdr ne, false)  
        end (* function lplet *)  
182    
183       and loop le =       and loop le =
184         (case le         (case le
185           of RET vs => (RET (map lpsv vs), true)      of SVAL v => SVAL(lpsv v)
186            | LET(vs, RET us, e) =>       | FN(v, t, e) => FN(v, t, loop e)
187                (ListPair.app chkIn (vs, map SimpVal us); loop e)       | APP(v1 as VAR x, v2) =>
188            | LET(vs, LET(us, e1, e2), e3) =>           (case selInfo x
189                loop(LET(us, e1, LET(vs, e2, e3)))             of SOME(ref c, FunExp(z,_,b)) =>
190            | LET(vs, FIX(fdecs, e1), e2) =>                 (if (c = 0) then loop(LET(z, SVAL v2, b))
191                loop(FIX(fdecs, LET(vs, e1, e2)))                  else bug "unexpected FunExp in APP")
192            | LET(vs, TFN(tfd, e1), e2) =>  (* commented out because it won't have any effect for the time being.
193                loop(TFN(tfd, LET(vs, e1, e2)))              | SOME(_, SimpVal (y as VAR _)) => loop(APP(y, v2))
194            | LET(vs, CON(dc, ts, u, v, e1), e2) =>  *)
195                loop(CON(dc, ts, u, v, LET(vs, e1, e2)))              | _ => APP(lpsv v1, lpsv v2))
196            | LET(vs, RECORD(rk, us, v, e1), e2) =>       | APP(v1, v2) => APP(lpsv v1, lpsv v2)
197                loop(RECORD(rk, us, v, LET(vs, e1, e2)))       | FIX(vs, ts, es, b) =>
198            | LET(vs, SELECT(u, i, v, e1), e2) =>           let fun g ((FN _)::r) = g r
199                loop(SELECT(u, i, v, LET(vs, e1, e2)))                 | g (_::r) = false
200            | LET(vs, PRIMOP(p, us, v, e1), e2) =>                 | g [] = true
201                loop(PRIMOP(p, us, v, LET(vs, e1, e2)))               val _ = if g es then () else bug "unexpected cases in loop-FIX"
202            | LET(vs, e1, e2 as (RET us)) =>               val _ = app (fn x => chkIn(x, SimpExp)) vs
203                if isEqs(vs, us) then loop e1               val nb = loop b
204                else let val (ne1, b1) = loop e1               val ws = map chkOut vs
205                         val nus = map lpsv us  
206                      in if (isDiffs(vs, nus)) andalso b1 then (RET nus, true)               fun h ((SOME(ref 0, _))::r) = h r
207                         else (LET(vs, ne1, RET nus), b1)                 | h (_::r) = false
208                     end                 | h [] = true
209            | LET(vs, e1, e2) =>            in if h ws then nb
210                let val _ = app (fn v => chkIn(v, StdExp)) vs               else FIX(vs, ts, map loop es, nb)
211                    val (ne1, b1) = loop e1           end
212                    val (ne2, b2) = loop e2       | LET(v, LET(u, e1, e2), e3) =>
213                 in if (alldead vs) andalso b1 then (ne2, b2)           loop(LET(u, e1, LET(v, e2, e3)))
214                    else (case ne2       | LET(v, FIX(vs, ts, es, b), e) =>
215                           of (RET us) =>           loop(FIX(vs, ts, es, LET(v, b, e)))
216                                if isEqs(vs, us) then (ne1, b1)       | LET(v, SVAL sv, e2) =>
217                                else (LET(vs, ne1, ne2), b1)           (chkIn(v, SimpVal sv); loop e2)
218                            | _ => (LET(vs, ne1, ne2), b1 andalso b2))       | LET(v, e1, e2 as SVAL (VAR x)) =>
219                end           if (v = x) then loop e1
220             else if isPure e1 then loop e2
221            | FIX(fdecs, e) =>                else LET(v, loop e1, loop e2)
222                let fun g (FK_FUN {isrec=SOME _, ...} :fkind, v, _, _) =       | LET(v, e1 as FN(v1, t1, b1), e2 as APP(VAR x, sv)) =>
223                           chkIn(v, StdExp)           if isDiff(v, sv) then
224                      | g ((_, v, vts, xe) : fundec) =             (if (v = x) then loop(LET(v1, SVAL sv, b1)) else loop e2)
225                           chkIn(v, if isCand v then FunExp(od, map #1 vts, xe)           else LET(v, loop e1, loop e2)
226                                    else StdExp)       | LET(v, e1, e2) =>
227                    val _ = app g fdecs           let val _ = chkIn(v, mkInfo(v,e1))
228                    val (ne, b) = loop e               val ne2 = loop e2
229                 in if alldead (map #2 fdecs) then (ne, b)               val w = chkOut v
230                    else (FIX(map lpfd fdecs, ne), b)            in case w
231                end                of SOME(_, CompExp) => LET(v, loop e1, ne2)
232            | APP(u, us) =>                 | SOME(ref 0, _) => ne2
233                (case appInfo u                 | _ => (case (e1, ne2)
234                  of SOME(od', vs, e) =>                          of (FN(v1,t1,b1), APP(VAR x, sv)) =>
235                       let val ne = LET(vs, RET us, e)                               if isDiff(v, sv) then
236                        in transform ((od, od', 0)::cfg) ne                                (if (v=x) then loop(LET(v1, SVAL sv,b1))
237                       end                                 else ne2)
238                   | _ => (APP(lpsv u, map lpsv us), false))                               else LET(v, loop e1, ne2)
239                             | (_, SVAL(VAR x)) =>
240            | TFN(tfdec as (v, tvks, xe), e) =>                               if isPure e1 then (if v=x then loop e1
241                lplet ((fn z => TFN((v, tvks,                                                  else ne2)
242                                #1(transform ((DI.next d, DI.next od,                               else LET(v, loop e1, ne2)
243                                              k+1)::rcfg) xe)), z)),                           | _ => LET(v, loop e1, ne2))
244                       true, v, StdExp, e)           end
245            | TAPP(u, ts) => (TAPP(lpsv u, map tcf ts), true)       | TFN(ks, e) => TFN(ks, loop e)
246         | TAPP(v, ts) => TAPP(lpsv v, ts)
247            | CON(c, ts, u, v, e) =>   (* this could be made more finegrain *)       | VECTOR(vs, t) => VECTOR(map lpsv vs, t)
248                lplet ((fn z => CON(lpdc c, map tcf ts, lpsv u, v, z)),       | RECORD vs => RECORD (map lpsv vs)
249                       true, v, ConExp(c,ts,u), e)       | SRECORD vs => SRECORD (map lpsv vs)
250         | SELECT(i, v as VAR x) =>
251             (case selInfo x
252               of SOME(_, ListExp vs) =>
253                    let val nv = List.nth(vs, i)
254                          handle _ => bug "unexpected List.Nth in SELECT"
255                     in SVAL(lpsv nv)
256                    end
257                | SOME(_, SimpVal (y as VAR _)) => loop(SELECT(i, y))
258                | _ => SELECT(i, lpsv v))
259         | SELECT(i, v) => SELECT(i, lpsv v)
260         | CON(c, ts, v) => CON(lpdc c, ts, lpsv v)
261         | DECON(c, ts, v) => DECON(lpdc c, ts, lpsv v)
262            | SWITCH (v, cs, ces, oe) =>            | SWITCH (v, cs, ces, oe) =>
263                (case swiInfo(v, ces, oe)           let val nv = lpsv v
264                  of SOME ne => loop ne               val nces = map (fn (c, e) => (lpcon c, loop e)) ces
265                   | _ => let val nv = lpsv v               val noe = case oe of NONE => NONE | SOME e => SOME (loop e)
266                              fun h ((c, e), (es, b)) =            in SWITCH(nv, cs, nces, noe)
267                                let val nc = lpcon c           end
268                                    val (ne, nb) = loop e       | ETAG(v, t) => ETAG(lpsv v, t)
269                                 in ((nc, ne)::es, nb andalso b)       | RAISE(v, t) => RAISE(lpsv v, t)
270                                end       | HANDLE(e, v) => HANDLE(loop e, lpsv v)
271                              val (nces, ncb) = foldr h ([], true) ces       | PACK(t, ts1, ts2, v) => PACK(t, ts1, ts2, lpsv v)
272                              val (noe, nb) =       | WRAP(t, b, v) => WRAP(t, b, lpsv v)
273                                case oe       | UNWRAP(t, b, v) => UNWRAP(t, b, lpsv v))
                                of NONE => (NONE, ncb)  
                                 | SOME e => let val (ne, b) = loop e  
                                              in (SOME ne, b andalso ncb)  
                                             end  
                          in (SWITCH(nv, cs, nces, noe), nb)  
                         end)  
   
           | RECORD (rk, us, v, e) =>  
               lplet ((fn z => RECORD(rk, map lpsv us, v, z)),  
                      true, v, ListExp us, e)  
           | SELECT(u, i, v, e) =>  
               (case selInfo (u, i)  
                 of SOME nv => (chkIn(v, SimpVal nv); loop e)  
                  | NONE => lplet ((fn z => SELECT(lpsv u, i, v, z)),  
                                   true, v, StdExp, e))  
   
           | RAISE(v, ts) => (RAISE(lpsv v, map ltf ts), false)  
           | HANDLE(e, v) =>  
               let val (ne, b) = loop e  
                in if b then (ne, true)  
                   else (HANDLE(ne, lpsv v), false)  
               end  
   
           | BRANCH(px as (d, p, lt, ts), vs, e1, e2) =>  
               let val (ne1, b1) = loop e1  
                   val (ne2, b2) = loop e2  
                in (BRANCH(case (d,ts) of (NONE, []) => px  
                                        | _ => (lpdt d, p, lt, map tcf ts),  
                           map lpsv vs, ne1, ne2), false)  
               end  
           | PRIMOP(px as (dt, p, lt, ts), vs, v, e) =>  
               lplet ((fn z => PRIMOP((case (dt, ts)  
                                        of (NONE, []) => px  
                                         | _ => (lpdt dt, p, lt, map tcf ts)),  
                                      map lpsv vs, v, z)),  
                      false (* isPure p *), v, StdExp, e))  
   
      in loop  
     end (* function transform *)  
   
 val d = DI.top  
 val (fk, f, vts, e) = fdec  
 in (fk, f, vts, #1 (transform [(d, d, 0)] e))  
    before (Intmap.clear m; cleanUp())  
 end (* function lcontract *)  
274    
275  (** run the lambda contraction twice *)  val nlexp = loop lexp
276  val lcontract = fn fdec => lcontract(lcontract(fdec, true), false)  in (Intmap.clear m; nlexp)
277    end
278    
279  end (* toplevel local *)  end (* toplevel local *)
280  end (* structure LContract *)  end (* structure LContract *)

Legend:
Removed from v.23  
changed lines
  Added in v.24

root@smlnj-gforge.cs.uchicago.edu
ViewVC Help
Powered by ViewVC 1.0.0