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

SCM Repository

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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4815 - (view) (download)

1 : jhr 3475 (* check-ir-fn.sml
2 : jhr 3470 *
3 :     * This code is part of the Diderot Project (http://diderot-language.cs.uchicago.edu)
4 :     *
5 :     * COPYRIGHT (c) 2015 The University of Chicago
6 :     * All rights reserved.
7 :     *
8 : jhr 3475 * Correctness checker for SSA-based IRs.
9 : jhr 3470 *
10 :     * TODO:
11 :     * check that the state variables and method stateOut variables are all defined.
12 : jhr 4317 * check that functions are defined
13 : jhr 3470 *)
14 :    
15 :     signature OPERATOR_TY =
16 :     sig
17 :     type rator
18 :     type ty
19 :    
20 : jhr 4265 (* returns the signature of a single-result operator as (rng, dom). *)
21 : jhr 3470 val sigOf : rator -> ty * ty list
22 :    
23 : jhr 4265 (* returns the signature of a multi-result operator as (rng, dom). *)
24 :     val msigOf : rator -> ty list * ty list
25 :    
26 : jhr 3470 (* return the type of a CONS, where the first argument is the annotated type
27 :     * and the second argument is the list of argument types. Returns false if
28 :     * there is a type error.
29 :     *)
30 :     val typeOfCons : ty * ty list -> bool
31 :    
32 : jhr 3937 (* return true if the type is of the specified form *)
33 :     val isBoolTy : ty -> bool
34 :     val isStrandTy : ty -> bool
35 :    
36 : jhr 3470 end
37 :    
38 : jhr 3475 functor CheckIRFn (
39 : jhr 3470
40 : jhr 3475 structure IR : SSA
41 : jhr 3470 structure OpTy : OPERATOR_TY
42 : jhr 3475 where type rator = IR.Op.rator
43 :     where type ty = IR.Ty.ty
44 : jhr 3470
45 :     ) : sig
46 :    
47 :     (* check the program for type errors, etc. The first argument will be used to
48 :     * identify the phase that the check follows and the return result will be true
49 :     * if any errors were detected.
50 :     *)
51 : jhr 3475 val check : string * IR.program -> bool
52 : jhr 3470
53 :     end = struct
54 :    
55 : jhr 3475 structure IR = IR
56 :     structure Ty = IR.Ty
57 :     structure V = IR.Var
58 : jhr 3470 structure VSet = V.Set
59 :    
60 :     (* forward analysis to determine the variables that are available in blocks *)
61 :     structure Avail = ForwardDFAFn (
62 :     struct
63 :    
64 : jhr 3475 structure IR = IR
65 : jhr 3470 type t = VSet.set
66 :    
67 :     val bottom = VSet.empty
68 :    
69 :     fun join inputs = List.foldl VSet.union bottom inputs
70 :    
71 : jhr 3475 fun transfer (input, nd as IR.ND{kind, ...}) = (case kind
72 :     of IR.JOIN{phis, ...} => let
73 : jhr 3470 (* add the lhs of the phi node. We do not remove the rhs variables, since
74 :     * after value numbering, they may have further uses.
75 :     *)
76 :     fun doPhi ((y, _), vs) = VSet.add(vs, y)
77 :     val output = List.foldl doPhi input (!phis)
78 :     in
79 :     output
80 :     end
81 : jhr 4317 | IR.FOREACH{var, phis, ...} => let
82 : jhr 3526 (* add the lhs of the phi node. We do not remove the rhs variables, since
83 :     * after value numbering, they may have further uses.
84 :     *)
85 :     fun doPhi ((y, _), vs) = VSet.add(vs, y)
86 :     val output = List.foldl doPhi input (!phis)
87 :     in
88 :     VSet.add (output, var)
89 :     end
90 : jhr 3475 | IR.ASSIGN{stm=(y, _), ...} => VSet.add(input, y)
91 : jhr 4362 | IR.MASSIGN{stm=(ys, _), ...} => VSet.addList(input, ys)
92 : jhr 3470 | _ => input
93 :     (* end case *))
94 :    
95 :     val same = VSet.equal
96 :    
97 :     fun toString vs = let
98 : jhr 3475 fun f (v, []) = [IR.Var.toString v, "}"]
99 :     | f (v, l) = IR.Var.toString v :: "," :: l
100 : jhr 3470 in
101 :     if VSet.isEmpty vs then "{}" else String.concat("{" :: VSet.foldl f [] vs)
102 :     end
103 :    
104 :     end)
105 :    
106 :     datatype token
107 : jhr 3475 = NL | S of string | A of Atom.atom | V of IR.var | VTYS of IR.var list
108 : jhr 3470 | TY of Ty.ty | TYS of Ty.ty list
109 : jhr 3475 | ND of IR.node
110 : jhr 3470
111 :     fun error errBuf toks = let
112 :     fun tok2str NL = "\n ** "
113 :     | tok2str (S s) = s
114 :     | tok2str (A s) = Atom.toString s
115 : jhr 4813 | tok2str (V x) = concat[V.toString x, "#", Int.toString(V.useCount x)]
116 : jhr 3470 | tok2str (VTYS xs) = tok2str(TYS(List.map V.ty xs))
117 :     | tok2str (TY ty) = Ty.toString ty
118 :     | tok2str (TYS []) = "()"
119 :     | tok2str (TYS[ty]) = Ty.toString ty
120 :     | tok2str (TYS tys) = String.concat[
121 :     "(", String.concatWith " * " (List.map Ty.toString tys), ")"
122 :     ]
123 : jhr 3475 | tok2str (ND nd) = IR.Node.toString nd
124 : jhr 3470 in
125 :     errBuf := concat ("**** Error: " :: List.map tok2str toks)
126 :     :: !errBuf
127 :     end
128 :    
129 : jhr 4806 (* property to track true use count of variables *)
130 :     local
131 :     val {getFn, setFn, clrFn, ...} = IR.Var.newProp (fn _ => 0)
132 :     in
133 :     fun incCnt x = let val n = getFn x in setFn(x, n+1) end
134 :     fun use x = (incCnt x; x)
135 :     val getCnt = getFn
136 :     val clrCnt = clrFn
137 :     end (* local *)
138 :    
139 :     (* property to mark if a variable has already been bound *)
140 :     local
141 :     val {getFn, setFn} = IR.Var.newFlag ()
142 :     in
143 :     val isBound = getFn
144 :     fun markAsBound x = setFn (x, true)
145 :     fun clrBound x = setFn (x, false)
146 :     end
147 :    
148 :     (* clear properties on a variable *)
149 :     fun clearVar x = (clrCnt x; clrBound x)
150 :    
151 :     fun checkAssign (errFn, bind) ((y, rhs), bvs) = let
152 : jhr 3470 (* check a variable use *)
153 : jhr 4806 fun checkVar x = if VSet.member(bvs, use x)
154 : jhr 3470 then ()
155 :     else errFn [
156 :     S "variable ", V x, S " is not bound in", NL,
157 : jhr 3475 S(IR.assignToString(y, rhs))
158 : jhr 3470 ]
159 :     fun tyError (ty1, ty2) = errFn [
160 : jhr 3475 S "type mismatch in \"", S(IR.assignToString (y, rhs)), S "\"",
161 : jhr 3470 NL, S "lhs: ", TY ty1, NL, S "rhs: ", TY ty2
162 :     ]
163 :     fun checkTys (ty1, ty2) = if Ty.same(ty1, ty2)
164 :     then ()
165 :     else tyError (ty1, ty2)
166 :     in
167 :     (* check that y is not bound twice *)
168 : jhr 4806 if isBound y
169 : jhr 3470 then errFn [
170 :     S "variable ", V y, S " is bound twice in", NL,
171 : jhr 3475 S(IR.assignToString (y, rhs))
172 : jhr 3470 ]
173 : jhr 4806 else bind y;
174 :     (* check RHS *)
175 : jhr 3470 case rhs
176 : jhr 3475 of IR.GLOBAL x => checkTys(V.ty y, IR.GlobalVar.ty x)
177 : jhr 3846 | IR.STATE(NONE, sv) => checkTys(V.ty y, IR.StateVar.ty sv)
178 : jhr 4317 | IR.STATE(SOME x, sv) => (
179 :     if not(OpTy.isStrandTy(V.ty x))
180 :     then errFn [
181 :     S "expected strand type for ", V x, S ", but found ", TY(V.ty x)
182 :     ]
183 :     else ();
184 :     checkVar x; checkTys(V.ty y, IR.StateVar.ty sv))
185 : jhr 3475 | IR.VAR x => (
186 : jhr 3470 checkVar x;
187 :     checkTys (V.ty y, V.ty x))
188 : jhr 3475 | IR.LIT lit => let
189 : jhr 3470 val ty = (case lit
190 :     of Literal.Int _ => Ty.intTy
191 :     | Literal.Real _ => Ty.realTy
192 :     | Literal.String _ => Ty.StringTy
193 :     | Literal.Bool _ => Ty.BoolTy
194 :     (* end case *))
195 :     in
196 :     checkTys (V.ty y, ty)
197 :     end
198 : jhr 3475 | IR.OP(rator, xs) => let
199 : jhr 3470 val (resTy, argTys) = OpTy.sigOf rator
200 :     in
201 :     List.app checkVar xs;
202 :     checkTys (V.ty y, resTy);
203 :     if ListPair.allEq (fn (x, ty) => Ty.same(V.ty x, ty)) (xs, argTys)
204 :     then ()
205 :     else errFn [
206 : jhr 3475 S "argument type mismatch in \"", S(IR.assignToString (y, rhs)), S "\"",
207 : jhr 3470 NL, S "expected: ", TYS argTys,
208 :     NL, S "found: ", VTYS xs
209 :     ]
210 :     end
211 : jhr 3475 | IR.CONS(xs, ty) => (
212 : jhr 3470 List.app checkVar xs;
213 :     if OpTy.typeOfCons (ty, List.map V.ty xs)
214 :     then checkTys (V.ty y, ty)
215 : jhr 3475 else errFn [S "invalid ", S(IR.assignToString(y, rhs))])
216 :     | IR.SEQ(xs, ty) => (
217 : jhr 3470 List.app checkVar xs;
218 :     (* FIXME: check types of sequence elements *)())
219 : jhr 4317 | IR.EINAPP(ein, xs) => (
220 : jhr 3470 List.app checkVar xs;
221 :     (* FIXME: check ein *)())
222 : jhr 4317 | IR.APPLY(f, xs) => (
223 :     List.app checkVar xs;
224 : jhr 4163 (* FIXME: check against type of f; check that f is defined *)
225 : jhr 4317 ())
226 : jhr 4806 | _ => errFn [S "bogus statement \"", S(IR.assignToString (y, rhs)), S "\""]
227 :     (* end case *)
228 : jhr 3470 end
229 :    
230 : jhr 4806 fun checkMAssign (errFn, bind) (stm as (ys, rhs), bvs) = let
231 : jhr 3470 (* check that a lhs variable is not bound twice *)
232 : jhr 4806 fun checkBind y = if isBound y
233 : jhr 3470 then errFn [
234 :     S "variable ", V y, S " is bound twice in", NL,
235 : jhr 3475 S(IR.massignToString stm)
236 : jhr 3470 ]
237 : jhr 4806 else bind y
238 : jhr 3470 (* check a variable use *)
239 : jhr 4806 fun checkVar x = if VSet.member(bvs, use x)
240 : jhr 3470 then ()
241 :     else errFn [
242 :     S "variable ", V x, S " is not bound in", NL,
243 : jhr 3475 S(IR.massignToString stm)
244 : jhr 3470 ]
245 : jhr 4806 in
246 :     (* check that the lhs variables are not bound twice *)
247 :     List.app checkBind ys;
248 :     (* check the rhs *)
249 :     case rhs
250 :     of IR.OP(rator, xs) => let
251 :     (* get the types *)
252 :     val (resTys, argTys) = OpTy.msigOf rator
253 :     in
254 :     List.app checkVar xs;
255 :     if ListPair.allEq (fn (y, ty) => Ty.same(V.ty y, ty)) (ys, resTys)
256 :     then ()
257 :     else errFn [
258 :     S "type mismatch in \"", S(IR.massignToString stm), S "\"", NL,
259 :     S "lhs: ", VTYS ys, NL,
260 :     S "rhs: ", TYS resTys
261 :     ];
262 :     if ListPair.allEq (fn (x, ty) => Ty.same(V.ty x, ty)) (xs, argTys)
263 :     then ()
264 :     else errFn [
265 :     S "argument type mismatch in \"", S(IR.massignToString stm), S "\"",
266 :     NL, S "expected: ", TYS argTys,
267 :     NL, S "found: ", VTYS xs
268 :     ]
269 :     end
270 :     | IR.MAPREDUCE mrs => let
271 : jhr 4809 fun chkMR (red, mapf, xs) = List.app checkVar xs
272 :     in
273 :     List.app chkMR mrs
274 :     end
275 : jhr 4806 | _ => errFn [S "bogus statement \"", S(IR.massignToString stm), S "\""]
276 :     (* end case *)
277 : jhr 3470 end
278 :    
279 : jhr 4806 fun checkPhi (errFn, bind) (nd, bvs, mask) (y, xs) = let
280 : jhr 3470 val ty = V.ty y
281 :     fun chkTy x = if Ty.same(V.ty x, ty)
282 :     then ()
283 :     else errFn [
284 : jhr 3475 S "type mismatch in \"", S(IR.phiToString (y, xs)), S "\"", NL,
285 : jhr 3470 S "typeof(", V y, S "): ", TY ty, NL,
286 :     S "typeof(", V x, S "): ", TY(V.ty x)
287 :     ]
288 :     fun chkRHS ([], []) = ()
289 :     | chkRHS (true::ms, NONE::xs) = chkRHS (ms, xs)
290 : jhr 4806 | chkRHS (false::ms, (SOME x)::xs) = (
291 :     if VSet.member(bvs, use x)
292 :     then ()
293 :     else errFn [
294 :     S "variable ", V x, S " is not bound in", NL,
295 :     S(IR.phiToString (y, xs))
296 :     ];
297 :     chkTy x;
298 :     chkRHS (ms, xs))
299 : jhr 3470 | chkRHS _ = errFn [S "phi/mask mismatch in ", ND nd]
300 :     in
301 :     (* check that y is not bound twice *)
302 : jhr 4806 if isBound y
303 : jhr 3470 then errFn [
304 :     S "variable ", V y, S " is bound twice in", NL,
305 : jhr 3475 S(IR.phiToString (y, xs))
306 : jhr 3470 ]
307 : jhr 4806 else bind y;
308 : jhr 3470 (* check that rhs vars have the correct type *)
309 :     chkRHS (mask, xs)
310 :     end
311 :    
312 : jhr 4806 fun checkUseCount errFn x = (
313 : jhr 4809 if (IR.Var.useCount x <> getCnt x)
314 :     then errFn [
315 :     S "incorrect use count ", S(Int.toString(IR.Var.useCount x)),
316 :     S " for ", V x, S " (actual count is ",
317 :     S(Int.toString(getCnt x)), S ")"
318 :     ]
319 :     else ();
320 :     clearVar x)
321 : jhr 4806
322 :     fun checkCFG errFn (vs, cfg) = let
323 : jhr 4809 (* compute the variables available on entry to each block *)
324 :     val nodes = Avail.analyse (VSet.fromList vs, cfg)
325 :     (* a list of the bound variables in the CFG *)
326 :     val bvs = ref []
327 :     val _ = List.app markAsBound vs
328 :     (* mark a variable as bound and add it to the bvs list *)
329 :     fun bind x = (markAsBound x; bvs := x :: !bvs)
330 :     (* specialize the assignment checking functions *)
331 : jhr 4806 val checkPhi = checkPhi (errFn, bind)
332 :     val checkAssign = checkAssign (errFn, bind)
333 :     val checkMAssign = checkMAssign (errFn, bind)
334 : jhr 4809 (* check the edges of a node. For each predecessor, the node should appear in the
335 :     * successor list and for each successor, the node should appear in the predecessor
336 :     * list.
337 :     *)
338 :     fun checkEdges nd = let
339 :     fun eqNd nd' = IR.Node.same(nd, nd')
340 :     fun chkPred src = if List.exists eqNd (IR.Node.succs src)
341 :     then ()
342 :     else errFn [
343 :     S "predecessor edge from ", ND nd, S " -> ", ND src,
344 :     S " not matched by successor edge"
345 :     ]
346 :     fun chkSucc dst = if List.exists eqNd (IR.Node.preds dst)
347 :     then ()
348 :     else errFn [
349 :     S "successor edge from ", ND nd, S " -> ", ND dst,
350 :     S " not matched by predecessor edge"
351 :     ]
352 :     in
353 :     List.app chkPred (IR.Node.preds nd);
354 :     List.app chkSucc (IR.Node.succs nd)
355 :     end
356 :     fun checkNd (nd as IR.ND{kind, ...}) = (case kind
357 :     of IR.NULL => errFn [S "unexpected ", ND nd]
358 :     | IR.JOIN{mask, phis, ...} =>
359 :     List.app
360 :     (checkPhi (nd, Avail.inValue nd, !mask))
361 :     (!phis)
362 :     | IR.COND{cond, ...} => (
363 :     if not(OpTy.isBoolTy(V.ty(!cond)))
364 :     then errFn [
365 :     S "expected bool type for ", V(!cond), S ", but found ",
366 :     TY(V.ty(!cond))
367 :     ]
368 :     else ();
369 :     if VSet.member(Avail.inValue nd, use (!cond))
370 :     then ()
371 :     else errFn [S "unbound variable ", V(!cond), S " in conditional"])
372 :     | IR.FOREACH{phis, mask, var, src, bodyExit, ...} => (
373 :     if isBound var
374 :     then errFn[]
375 :     else bind var;
376 :     if VSet.member(Avail.inValue nd, use(!src))
377 :     then ()
378 :     else errFn [S "unbound variable ", V(!src), S " in foreach"];
379 :     List.app (checkPhi (nd, Avail.inValue nd, !mask)) (!phis);
380 :     case IR.Node.kind(!bodyExit)
381 :     of IR.NEXT{succ, ...} =>
382 :     if IR.Node.same(nd, !succ)
383 :     then ()
384 :     else errFn [
385 :     S "loop's body exit node ", ND(!bodyExit),
386 :     S " does not point back to loop header"
387 :     ]
388 :     | _ => errFn [
389 :     S "bad bodyExit ", ND(!bodyExit), S " for loop ", ND nd
390 :     ]
391 :     (* end case *))
392 :     | IR.NEXT{succ, ...} => (case IR.Node.kind(!succ)
393 :     of IR.FOREACH{bodyExit, ...} =>
394 :     if IR.Node.same(nd, !bodyExit)
395 :     then ()
396 :     else errFn [
397 :     S "next node ", ND nd, S " and loop ", ND(!succ),
398 :     S " do not agree"
399 :     ]
400 :     | _ => errFn [
401 :     S "bad successor ", ND(!succ), S " for loop exit ", ND nd
402 :     ]
403 :     (* end case *))
404 :     | IR.ASSIGN{stm, ...} =>
405 :     checkAssign (stm, Avail.inValue nd)
406 :     | IR.MASSIGN{stm, ...} =>
407 :     checkMAssign (stm, Avail.inValue nd)
408 :     | IR.GASSIGN{lhs, rhs, ...} => let
409 :     val avail = Avail.inValue nd
410 :     in
411 :     if VSet.member(avail, use rhs)
412 :     then ()
413 :     else errFn [
414 :     S "variable ", V rhs, S " is not bound in global assignment ",
415 :     S(IR.GlobalVar.toString lhs)
416 :     ];
417 :     if Ty.same(IR.GlobalVar.ty lhs, V.ty rhs)
418 :     then ()
419 :     else errFn [
420 :     S "type mismatch in \"", S(IR.GlobalVar.toString lhs),
421 :     S " = ", S(V.toString rhs), S "\"",
422 :     NL, S "lhs: ", TY(IR.GlobalVar.ty lhs),
423 :     NL, S "rhs: ", TY(V.ty rhs)
424 :     ]
425 :     end
426 :     | IR.NEW{strand, args, ...} => let
427 :     val avail = Avail.inValue nd
428 :     (* check a variable use *)
429 :     fun checkVar x = if VSet.member(avail, use x)
430 :     then ()
431 :     else errFn [
432 :     S "variable ", V x, S " is not bound in new ",
433 :     S(Atom.toString strand)
434 :     ]
435 :     in
436 :     List.app checkVar args
437 :     end
438 :     | IR.SAVE{lhs, rhs, ...} => let
439 :     val avail = Avail.inValue nd
440 :     in
441 :     if VSet.member(avail, use rhs)
442 :     then ()
443 :     else errFn [
444 :     S "variable ", V rhs, S " is not bound in save ",
445 :     S(IR.StateVar.toString lhs)
446 :     ];
447 :     if Ty.same(IR.StateVar.ty lhs, V.ty rhs)
448 :     then ()
449 :     else errFn [
450 :     S "type mismatch in \"", S(IR.StateVar.toString lhs),
451 :     S " = ", S(V.toString rhs), S "\"",
452 :     NL, S "lhs: ", TY(IR.StateVar.ty lhs),
453 :     NL, S "rhs: ", TY(V.ty rhs)
454 :     ]
455 :     end
456 :     | IR.EXIT{kind=ExitKind.RETURN(SOME x), ...} =>
457 :     if VSet.member(Avail.inValue nd, use x)
458 :     then ()
459 :     else errFn [
460 :     S "variable ", V x, S " is not bound in return"
461 :     ]
462 :     | _ => ()
463 :     (* end case *))
464 :     in
465 :     List.app checkEdges nodes;
466 :     List.app checkNd nodes;
467 :     (* clean up variables and check use counts *)
468 :     List.app (checkUseCount errFn) (!bvs);
469 :     (* cleanup *)
470 :     Avail.scrub nodes
471 :     end (* checkCFG *)
472 : jhr 4806
473 : jhr 3485 fun check (phase, prog) = let
474 : jhr 4317 val IR.Program{
475 :     props, consts, inputs, globals, funcs,
476 : jhr 4491 constInit, globInit, strand, create, start, update
477 : jhr 4317 } = prog
478 : jhr 3470 val errBuf = ref []
479 :     val errFn = error errBuf
480 :     fun final () = (case !errBuf
481 :     of [] => false
482 :     | errs => (
483 : jhr 3676 Log.msg ["********** IR Errors detected after ", phase, " **********\n"];
484 :     List.app (fn msg => Log.msg [msg, "\n"]) (List.rev errs);
485 : jhr 3470 true)
486 :     (* end case *))
487 : jhr 4809 val checkCFG = checkCFG errFn
488 : jhr 4317 (* check a function definition *)
489 :     fun checkFunc (IR.Func{name, params, body}) = checkCFG (params, body)
490 : jhr 3470 (* check a strand definition *)
491 : jhr 4491 fun checkStrand (IR.Strand{params, state, stateInit, startM, updateM, stabilizeM, ...}) =
492 : jhr 4317 let
493 : jhr 3470 val nStateVars = List.length state
494 : jhr 4163 fun checkMethod body = checkCFG (params, body)
495 : jhr 3470 (*DEBUG*)handle ex => raise ex
496 :     in
497 : jhr 4163 checkCFG (params, stateInit)
498 : jhr 3470 (*DEBUG*)handle ex => raise ex;
499 : jhr 4491 Option.app checkMethod startM;
500 : jhr 4317 checkMethod updateM;
501 : jhr 4806 Option.app checkMethod stabilizeM;
502 : jhr 4809 List.app (checkUseCount errFn) params
503 : jhr 3470 end
504 :     (* handle exceptions *)
505 :     fun onExn exn =
506 : jhr 4317 errFn (S "uncaught exception: " :: S(exnMessage exn) ::
507 :     List.foldr (fn (s, msg) => NL :: S " raised at " :: S s :: msg)
508 :     [] (SMLofNJ.exnHistory exn))
509 :     fun checkCFG' cfg = (checkCFG ([], cfg) handle ex => onExn ex)
510 : jhr 3470 in
511 :     (* check the input part *)
512 : jhr 3485 checkCFG' constInit;
513 : jhr 4317 (* check functions *)
514 :     List.app checkFunc funcs;
515 : jhr 3470 (* check the global part *)
516 : jhr 3995 checkCFG' globInit;
517 : jhr 4317 (* check initial strand creation *)
518 :     Create.app checkCFG' create;
519 : jhr 3470 (* check the strands *)
520 : jhr 3485 (checkStrand strand) handle ex => onExn ex;
521 : jhr 4491 (* check the optional global start *)
522 :     Option.app checkCFG' start;
523 : jhr 4317 (* check the optional global update *)
524 :     Option.app checkCFG' update;
525 : jhr 3470 (* check for errors *)
526 :     final()
527 :     end
528 :    
529 :     end

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