Home My Page Projects Code Snippets Project Openings diderot
Summary Activity Tracker Tasks SCM

SCM Repository

[diderot] Annotation of /branches/vis15/src/compiler/tree-ir/check-tree.sml
ViewVC logotype

Annotation of /branches/vis15/src/compiler/tree-ir/check-tree.sml

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3861 - (view) (download)

1 : jhr 3754 (* check-tree.sml
2 :     *
3 :     * This code is part of the Diderot Project (http://diderot-language.cs.uchicago.edu)
4 :     *
5 :     * COPYRIGHT (c) 2016 The University of Chicago
6 :     * All rights reserved.
7 :     *
8 :     * TODO: check global and state variable consistency
9 :     *)
10 :    
11 : jhr 3859 (* FIXME: the cehcking function should be parameterized over the vector layout of the target *)
12 : jhr 3757
13 : jhr 3754 structure CheckTree : sig
14 :    
15 :     val check : string * TreeIR.program -> bool
16 :    
17 :     end = struct
18 :    
19 :     structure IR = TreeIR
20 :     structure Op = TreeOps
21 : jhr 3861 structure Ty = TreeTypes
22 : jhr 3832 structure GVar = TreeGlobalVar
23 :     structure SVar = TreeStateVar
24 :     structure Var = TreeVar
25 : jhr 3754 structure VSet = Var.Set
26 :    
27 :     datatype token
28 :     = NL | S of string | A of Atom.atom | V of IR.var
29 : jhr 3810 | TY of Ty.t | TYS of Ty.t list
30 : jhr 3754
31 :     fun error errBuf toks = let
32 :     fun tok2str NL = "\n ** "
33 :     | tok2str (S s) = s
34 :     | tok2str (A s) = Atom.toString s
35 :     | tok2str (V x) = Var.toString x
36 :     | tok2str (TY ty) = Ty.toString ty
37 :     | tok2str (TYS []) = "()"
38 :     | tok2str (TYS[ty]) = Ty.toString ty
39 :     | tok2str (TYS tys) = String.concat[
40 :     "(", String.concatWith " * " (List.map Ty.toString tys), ")"
41 :     ]
42 :     in
43 :     errBuf := concat ("**** Error: " :: List.map tok2str toks)
44 :     :: !errBuf
45 :     end
46 :    
47 : jhr 3757 (* utility function for synthesizing eigenvector/eigenvalue signature *)
48 :     fun eigenSig dim = let
49 :     val tplTy = Ty.TupleTy[
50 :     Ty.SeqTy(Ty.realTy, SOME dim),
51 : jhr 3861 Ty.SeqTy(Ty.TensorTy[dim], SOME dim)
52 : jhr 3757 ]
53 :     in
54 : jhr 3767 (* FIXME: what about pieces? *)
55 : jhr 3833 (tplTy, [Ty.TensorTy[dim, dim]])
56 : jhr 3757 end
57 :    
58 : jhr 3861 exception BadVecType of int
59 :    
60 : jhr 3757 (* Return the signature of a TreeIR operator. *)
61 : jhr 3861 fun sigOfOp (vecTy, rator) = (case rator
62 : jhr 3757 of Op.IAdd => (Ty.IntTy, [Ty.IntTy, Ty.IntTy])
63 :     | Op.ISub => (Ty.IntTy, [Ty.IntTy, Ty.IntTy])
64 :     | Op.IMul => (Ty.IntTy, [Ty.IntTy, Ty.IntTy])
65 :     | Op.IDiv => (Ty.IntTy, [Ty.IntTy, Ty.IntTy])
66 :     | Op.IMod => (Ty.IntTy, [Ty.IntTy, Ty.IntTy])
67 :     | Op.INeg => (Ty.IntTy, [Ty.IntTy])
68 :     | Op.RAdd => (Ty.realTy, [Ty.realTy, Ty.realTy])
69 :     | Op.RSub => (Ty.realTy, [Ty.realTy, Ty.realTy])
70 :     | Op.RMul => (Ty.realTy, [Ty.realTy, Ty.realTy])
71 :     | Op.RDiv => (Ty.realTy, [Ty.realTy, Ty.realTy])
72 :     | Op.RNeg => (Ty.realTy, [Ty.realTy])
73 : jhr 3830 | Op.RClamp => (Ty.realTy, [Ty.realTy, Ty.realTy, Ty.realTy])
74 :     | Op.RLerp => (Ty.realTy, [Ty.realTy, Ty.realTy, Ty.realTy])
75 : jhr 3757 | Op.LT ty => (Ty.BoolTy, [ty, ty])
76 :     | Op.LTE ty => (Ty.BoolTy, [ty, ty])
77 :     | Op.EQ ty => (Ty.BoolTy, [ty, ty])
78 :     | Op.NEQ ty => (Ty.BoolTy, [ty, ty])
79 :     | Op.GT ty => (Ty.BoolTy, [ty, ty])
80 :     | Op.GTE ty => (Ty.BoolTy, [ty, ty])
81 :     | Op.Not => (Ty.BoolTy, [Ty.BoolTy])
82 :     | Op.Abs ty => (ty, [ty])
83 :     | Op.Max ty => (ty, [ty, ty])
84 :     | Op.Min ty => (ty, [ty, ty])
85 : jhr 3861 | Op.VAdd d => (vecTy d, [vecTy d, vecTy d])
86 :     | Op.VSub d => (vecTy d, [vecTy d, vecTy d])
87 :     | Op.VScale d => (vecTy d, [Ty.realTy, vecTy d])
88 :     | Op.VMul d => (vecTy d, [vecTy d, vecTy d])
89 :     | Op.VNeg d => (vecTy d, [vecTy d])
90 :     | Op.VSum d => (Ty.realTy, [vecTy d])
91 :     | Op.VIndex(n, _) => (Ty.realTy, [vecTy n])
92 :     | Op.VClamp d => (vecTy d, [vecTy d, Ty.realTy, Ty.realTy])
93 :     | Op.VMapClamp d => (vecTy d, [vecTy d, vecTy d, vecTy d])
94 :     | Op.VLerp d => (vecTy d, [vecTy d, vecTy d, Ty.realTy])
95 : jhr 3853 | Op.TensorIndex(ty, _) => (Ty.realTy, [ty])
96 :     | Op.ProjectLast(ty as Ty.TensorTy dd, _) => (Ty.TensorTy[List.last dd], [ty])
97 : jhr 3757 | Op.EigenVecs2x2 => eigenSig 2
98 :     | Op.EigenVecs3x3 => eigenSig 3
99 : jhr 3767 (* FIXME: what about pieces? *)
100 : jhr 3833 | Op.EigenVals2x2 => (Ty.SeqTy(Ty.realTy, SOME 2), [Ty.TensorTy[2, 2]])
101 :     | Op.EigenVals3x3 => (Ty.SeqTy(Ty.realTy, SOME 3), [Ty.TensorTy[3, 3]])
102 : jhr 3757 | Op.Zero ty => (ty, [])
103 :     | Op.Select(ty as Ty.TupleTy tys, i) => (List.nth(tys, i-1), [ty])
104 :     | Op.Subscript(ty as Ty.SeqTy(elemTy, _)) => (elemTy, [ty, Ty.intTy])
105 :     | Op.MkDynamic(ty, n) => (Ty.SeqTy(ty, NONE), [Ty.SeqTy(ty, SOME n)])
106 :     | Op.Prepend ty => (Ty.SeqTy(ty, NONE), [ty, Ty.SeqTy(ty, NONE)])
107 :     | Op.Append ty => (Ty.SeqTy(ty, NONE), [Ty.SeqTy(ty, NONE), ty])
108 :     | Op.Concat ty => (Ty.SeqTy(ty, NONE), [Ty.SeqTy(ty, NONE), Ty.SeqTy(ty, NONE)])
109 :     | Op.Range => (Ty.SeqTy(Ty.intTy, NONE), [Ty.IntTy, Ty.IntTy])
110 :     | Op.Length ty => (Ty.intTy, [Ty.SeqTy(ty, NONE)])
111 :     | Op.SphereQuery(ptTy, strandTy) => (Ty.SeqTy(strandTy, NONE), [ptTy, Ty.realTy])
112 :     | Op.Sqrt => (Ty.realTy, [Ty.realTy])
113 :     | Op.Cos => (Ty.realTy, [Ty.realTy])
114 :     | Op.ArcCos => (Ty.realTy, [Ty.realTy])
115 : jhr 3859 | Op.Sin => (Ty.realTy, [Ty.realTy])
116 : jhr 3757 | Op.ArcSin => (Ty.realTy, [Ty.realTy])
117 :     | Op.Tan => (Ty.realTy, [Ty.realTy])
118 :     | Op.ArcTan => (Ty.realTy, [Ty.realTy])
119 :     | Op.Exp => (Ty.realTy, [Ty.realTy])
120 : jhr 3861 | Op.Ceiling d => (vecTy d, [vecTy d])
121 :     | Op.Floor d => (vecTy d, [vecTy d])
122 :     | Op.Round d => (vecTy d, [vecTy d])
123 :     | Op.Trunc d => (vecTy d, [vecTy d])
124 : jhr 3757 | Op.IntToReal => (Ty.realTy, [Ty.intTy])
125 :     | Op.RealToInt 1 => (Ty.IntTy, [Ty.realTy])
126 : jhr 3861 | Op.RealToInt d => (Ty.SeqTy(Ty.IntTy, SOME d), [vecTy d])
127 : jhr 3757 (* not sure if we will need these
128 :     | R_All of ty
129 :     | R_Exists of ty
130 :     | R_Max of ty
131 :     | R_Min of ty
132 :     | R_Sum of ty
133 :     | R_Product of ty
134 :     | R_Mean of ty
135 :     | R_Variance of ty
136 :     *)
137 : jhr 3766 (* FIXME: these should probably be compiled down to lower-level operartions at this point!
138 : jhr 3757 | Op.Transform info => let
139 :     val dim = ImageInfo.dim info
140 :     in
141 :     if (dim = 1)
142 : jhr 3766 then (Ty.realTy, [Ty.ImageTy info])
143 :     else (Ty.matrixTy(dim, dim), [Ty.ImageTy info])
144 : jhr 3757 end
145 :     | Op.Translate info => let
146 :     val dim = ImageInfo.dim info
147 :     in
148 :     if (dim = 1)
149 : jhr 3766 then (Ty.realTy, [Ty.ImageTy info])
150 :     else (Ty.matrixTy(dim, dim), [Ty.ImageTy info])
151 : jhr 3757 end
152 :     *)
153 : jhr 3766 | Op.ControlIndex(info, _, _) => (Ty.IntTy, [Ty.ImageTy info, Ty.IntTy])
154 : jhr 3861 | Op.Inside(info, _) => (Ty.BoolTy, [vecTy(ImageInfo.dim info), Ty.ImageTy info])
155 : jhr 3757 | Op.ImageDim(info, _) => (Ty.IntTy, [Ty.ImageTy info])
156 :     | Op.LoadSeq(ty, _) => (ty, [])
157 :     | Op.LoadImage(ty, _) => (ty, [])
158 : jhr 3859 | Op.MathFn f => MathFns.sigOf (Ty.realTy, f)
159 : jhr 3757 | _ => raise Fail("sigOf: invalid operator " ^ Op.toString rator)
160 :     (* end case *))
161 :    
162 : jhr 3754 fun check (phase, prog) = let
163 :     val IR.Program{
164 : jhr 3861 props, target={layout, ...}, consts, inputs, constInit,
165 :     globals, globalInit, strand, create, update
166 : jhr 3754 } = prog
167 :     val errBuf = ref []
168 :     val errFn = error errBuf
169 :     fun final () = (case !errBuf
170 :     of [] => false
171 :     | errs => (
172 :     Log.msg ["********** IR Errors detected after ", phase, " **********\n"];
173 :     List.app (fn msg => Log.msg [msg, "\n"]) (List.rev errs);
174 :     true)
175 :     (* end case *))
176 : jhr 3861 fun sigOf rator = let
177 :     fun vecTy d = (case layout d
178 :     of {padded, pieces=[w], ...} => Ty.VecTy(d, w)
179 :     | _ => (
180 :     errFn [
181 :     S "invalid width ", S(Int.toString d), S " for ", S(Op.toString rator)
182 :     ];
183 :     Ty.VecTy(d,d))
184 :     (* end case *))
185 :     in
186 :     sigOfOp (vecTy, rator)
187 :     end
188 : jhr 3754 (* check a variable use *)
189 :     fun checkVar (bvs, x) = if VSet.member(bvs, x)
190 :     then ()
191 :     else errFn [S "variable ", V x, S " is not bound"]
192 : jhr 3861 fun chkBlock (bvs : VSet.set, IR.Block{locals, body}) = let
193 :     fun chkExp (bvs : VSet.set, e) = let
194 : jhr 3754 fun chk e = (case e
195 :     of IR.E_Global gv => GVar.ty gv
196 : jhr 3861 | IR.E_State(NONE, sv) => SVar.ty sv
197 :     | IR.E_State(SOME e, sv) => (
198 :     (* FIXME: check type of e *)
199 :     SVar.ty sv)
200 : jhr 3859 | IR.E_Var x => (checkVar(bvs, x); Var.ty x)
201 : jhr 3754 | IR.E_Lit(Literal.Int _) => Ty.IntTy
202 :     | IR.E_Lit(Literal.Real _) => Ty.realTy
203 :     | IR.E_Lit(Literal.String _) => Ty.StringTy
204 :     | IR.E_Lit(Literal.Bool _) => Ty.BoolTy
205 :     | IR.E_Op(rator, args) => let
206 : jhr 3757 val (resTy, paramTys) = sigOf rator
207 : jhr 3754 val argTys = List.map chk args
208 :     in
209 :     if ListPair.allEq Ty.same (paramTys, argTys)
210 :     then ()
211 :     else errFn [
212 :     S "argument type mismatch in application of ",
213 :     S(Op.toString rator),
214 :     NL, S "expected: ", TYS paramTys,
215 :     NL, S "found: ", TYS argTys
216 :     ];
217 :     resTy
218 :     end
219 : jhr 3861 | IR.E_Vec es => let
220 :     fun chkArg (i, e) = (case chk e
221 :     of Ty.VecTy(1, 1) => () (* ok *)
222 :     | ty => errFn [
223 :     S "component ", S(Int.toString i),
224 :     S " of vector does has type ", TY ty
225 :     ])
226 :     (* check the result vector type *)
227 :     val ty = (case layout(List.length es)
228 :     of {wid, padded, pieces=[w]} => Ty.VecTy(wid, w)
229 :     | {wid, ...} => (
230 :     errFn [
231 :     S "invalid width ", S(Int.toString wid),
232 :     S " for E_Vec"
233 :     ];
234 :     Ty.VecTy(wid, wid))
235 :     (* end case *))
236 :     in
237 :     List.appi chkArg es;
238 :     ty
239 :     end
240 : jhr 3754 | IR.E_Cons([], ty) => (
241 :     errFn [S "empty cons"];
242 :     ty)
243 : jhr 3833 | IR.E_Cons(es, consTy as Ty.TensorTy dd) => let
244 :     val nelems = List.foldl Int.* 1 dd
245 :     in
246 :     if (length es <> nelems)
247 :     then errFn [
248 :     S "cons has incorrect number of elements",
249 :     NL, S " expected: ", S(Int.toString nelems),
250 :     NL, S " found: ", S(Int.toString(length es))
251 :     ]
252 :     else ();
253 : jhr 3859 chkElems ("cons", Ty.realTy, es);
254 : jhr 3833 consTy
255 :     end
256 : jhr 3768 | IR.E_Cons(es, ty) => (
257 :     errFn [S "unexpected type for cons: ", TY ty];
258 :     ty)
259 : jhr 3754 | IR.E_Seq([], ty as Ty.SeqTy(_, SOME 0)) => ty
260 :     | IR.E_Seq([], ty as Ty.SeqTy(_, SOME n)) => (
261 :     errFn [S "empty sequence, but expected ", TY ty];
262 :     ty)
263 :     | IR.E_Seq(es, seqTy as Ty.SeqTy(ty, NONE)) => (
264 :     chkElems ("sequence", ty, es);
265 :     seqTy)
266 :     | IR.E_Seq(es, seqTy as Ty.SeqTy(ty, SOME n)) => (
267 :     if (length es <> n)
268 :     then errFn [
269 :     S "sequence has incorrect number of elements",
270 :     NL, S " expected: ", S(Int.toString n),
271 :     NL, S " found: ", S(Int.toString(length es))
272 :     ]
273 :     else ();
274 :     chkElems ("sequence", ty, es);
275 :     seqTy)
276 :     | IR.E_Seq(es, ty) => (
277 :     errFn [S "unexpected type for sequence: ", TY ty];
278 :     ty)
279 : jhr 3861 | IR.E_Pack(layout, es) => let
280 :     fun chkOne (i, ty, ty') = if Ty.same(ty, ty')
281 :     then ()
282 :     else errFn[
283 :     S "mismatch in component ", S(Int.toString i),
284 :     S " of PACK",
285 :     NL, S " expected: ", TY ty',
286 :     NL, S " found: ", TY ty
287 :     ]
288 :     in
289 :     ListPair.appi chkOne (List.map chk es, Ty.piecesOf layout);
290 :     Ty.TensorTy[#wid layout]
291 :     end
292 :     | IR.E_VLoad(layout, e, i) => let
293 :     val ty = chk e
294 :     val expectedTy = Ty.TensorTy[#wid layout]
295 :     in
296 :     if Ty.same(ty, expectedTy)
297 :     then ()
298 :     else errFn [
299 :     S "type mismatch in E_VLoad",
300 :     NL, S " expected: ", TY expectedTy,
301 :     NL, S " found: ", TY ty
302 :     ];
303 :     Ty.nthVec(layout, i)
304 :     end
305 : jhr 3754 (* end case *))
306 :     and chkElems (cxt, ty, []) = ()
307 :     | chkElems (cxt, ty, e::es) = let
308 :     val ty' = chk e
309 :     in
310 :     if Ty.same(ty, ty')
311 :     then ()
312 :     else errFn [
313 :     S "element of ", S cxt, S " has incorrect type",
314 :     NL, S "expected: ", TY ty,
315 :     NL, S "found: ", TY ty'
316 :     ];
317 :     chkElems (cxt, ty, es)
318 :     end
319 :     in
320 :     chk e
321 :     end
322 : jhr 3861 fun chkStm (stm, bvs : VSet.set) = (case stm
323 : jhr 3754 of IR.S_Comment _ => bvs
324 : jhr 3857 | IR.S_Assign(isDef, x, e) => let
325 : jhr 3767 val ty = chkExp (bvs, e)
326 :     in
327 :     if Ty.same(Var.ty x, ty)
328 :     then ()
329 :     else errFn[
330 :     S "type mismatch in assignment to local ", S(Var.name x),
331 :     NL, S "lhs: ", TY(Var.ty x),
332 :     NL, S "rhs: ", TY ty
333 :     ];
334 : jhr 3859 if isDef
335 :     then VSet.add(bvs, x)
336 :     else (checkVar(bvs, x); bvs)
337 : jhr 3767 end
338 : jhr 3859 | IR.S_MAssign(xs, e) => raise Fail "FIXME"
339 : jhr 3754 | IR.S_GAssign(gv, e) => let
340 :     val ty = chkExp (bvs, e)
341 :     in
342 :     if Ty.same(GVar.ty gv, ty)
343 :     then ()
344 :     else errFn[
345 :     S "type mismatch in assignment to global ", S(GVar.name gv),
346 :     NL, S "lhs: ", TY(GVar.ty gv),
347 :     NL, S "rhs: ", TY ty
348 :     ];
349 :     bvs
350 :     end
351 :     | IR.S_IfThen(e, b) => let
352 :     val ty = chkExp (bvs, e)
353 :     in
354 :     if Ty.same(ty, Ty.BoolTy)
355 :     then ()
356 :     else errFn[
357 :     S "expected bool for if-then, but found ", TY ty
358 :     ];
359 :     chkBlock (bvs, b);
360 :     bvs
361 :     end
362 :     | IR.S_IfThenElse(e, b1, b2) => let
363 :     val ty = chkExp (bvs, e)
364 :     in
365 :     if Ty.same(ty, Ty.BoolTy)
366 :     then ()
367 :     else errFn[
368 :     S "expected bool for if-then-else, but found ", TY ty
369 :     ];
370 :     chkBlock (bvs, b1);
371 :     chkBlock (bvs, b2);
372 :     bvs
373 :     end
374 :     | IR.S_Foreach(x, e, b) => (
375 :     case chkExp (bvs, e)
376 :     of Ty.SeqTy(ty, _) =>
377 :     if Ty.same(ty, Var.ty x)
378 :     then ()
379 :     else errFn [
380 :     S "type mismatch in foreach ", V x,
381 :     NL, S "variable type: ", TY(Var.ty x),
382 :     NL, S "domain type: ", TY ty
383 :     ]
384 :     | ty => errFn [
385 :     S "domain of foreach is not sequence type; found ", TY ty
386 :     ]
387 :     (* end case *);
388 :     ignore (chkBlock (VSet.add(bvs, x), b));
389 :     bvs)
390 : jhr 3859 | IR.S_LoadNrrd(x, name) => bvs (* FIXME: check type of x *)
391 : jhr 3754 | IR.S_Input(gv, _, _, NONE) => bvs
392 :     | IR.S_Input(gv, _, _, SOME e) => let
393 :     val ty = chkExp (bvs, e)
394 :     in
395 :     if Ty.same(GVar.ty gv, ty)
396 :     then ()
397 :     else errFn[
398 :     S "type mismatch in default for input ", S(GVar.name gv),
399 :     NL, S "expected: ", TY(GVar.ty gv),
400 :     NL, S "found: ", TY ty
401 :     ];
402 :     bvs
403 :     end
404 :     | IR.S_InputNrrd(gv, _, _, _) => (
405 :     case GVar.ty gv
406 :     of Ty.SeqTy(_, NONE) => ()
407 :     | Ty.ImageTy _ => ()
408 :     | ty => errFn [
409 :     S "input variable ", S(GVar.name gv), S " has bogus type ",
410 :     TY ty, S " for lhs for InputNrrd"
411 :     ]
412 :     (* end case *);
413 :     bvs)
414 :     | IR.S_New(_, es) => (
415 :     List.app (fn e => ignore (chkExp(bvs, e))) es;
416 :     bvs)
417 : jhr 3767 | IR.S_Save(sv, e) => let
418 :     val ty = chkExp (bvs, e)
419 : jhr 3754 in
420 : jhr 3767 if Ty.same(SVar.ty sv, ty)
421 :     then ()
422 :     else errFn[
423 :     S "type mismatch in assignment to state variable ",
424 :     S(SVar.name sv),
425 :     NL, S "lhs: ", TY(SVar.ty sv),
426 :     NL, S "rhs: ", TY ty
427 :     ];
428 : jhr 3754 bvs
429 :     end
430 : jhr 3859 | IR.S_Exit => bvs
431 : jhr 3768 | IR.S_Print(tys, es) => (
432 :     if (length tys <> length es)
433 :     then errFn [
434 :     ]
435 :     else ();
436 :     ListPair.appi
437 :     (fn (i, ty, e) => let val ty' = chkExp(bvs, e)
438 :     in
439 :     if Ty.same(ty, ty')
440 :     then ()
441 :     else errFn[
442 :     S "type mismatch in argument ", S(Int.toString i),
443 :     S " of print",
444 :     NL, S "expected: ", TY ty,
445 :     NL, S "but found: ", TY ty'
446 :     ]
447 :     end)
448 :     (tys, es);
449 :     bvs)
450 : jhr 3754 | IR.S_Active => bvs
451 :     | IR.S_Stabilize => bvs
452 :     | IR.S_Die => bvs
453 :     (* end case *))
454 : jhr 3861 val bvs = VSet.addList(bvs, !locals)
455 : jhr 3754 in
456 :     ignore (List.foldl chkStm bvs body)
457 :     end
458 :     fun chkOptBlock (_, NONE) = ()
459 :     | chkOptBlock (bvs, SOME blk) = ignore (chkBlock (bvs, blk))
460 :     fun chkStrand (IR.Strand{name, params, state, stateInit, initM, updateM, stabilizeM}) = (
461 :     ignore (chkBlock (VSet.fromList params, stateInit));
462 :     chkOptBlock (VSet.empty, initM);
463 :     ignore (chkBlock (VSet.empty, updateM));
464 :     chkOptBlock (VSet.empty, stabilizeM))
465 :     in
466 :     ignore (chkBlock (VSet.empty, constInit));
467 :     ignore (chkBlock (VSet.empty, globalInit));
468 :     chkStrand strand;
469 :     case create of IR.Create{code, ...} => ignore (chkBlock (VSet.empty, code));
470 :     chkOptBlock (VSet.empty, update);
471 :     final ()
472 :     end
473 :    
474 :     end

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