caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* Re: [Caml-list] Error Reporting
@ 2001-09-27 13:32 Krishnaswami, Neel
  2001-09-27 20:36 ` David McClain
  2001-09-27 22:34 ` David McClain
  0 siblings, 2 replies; 11+ messages in thread
From: Krishnaswami, Neel @ 2001-09-27 13:32 UTC (permalink / raw)
  To: caml-list

David McClain [mailto:barabh@qwest.net] wrote:
> 
> I am looking for some ideas on handling program error reporting for
> a compiler that is built largely on CPS. Since there are no stack
> frames to crawl for traceback, I have a first cut based on keeping a
> finite length queue of last visited closures. But this becomes
> problematic when there are circularities in the code, i.e.,
> recursion. Also, I think I have way too much error tracking going on
> at each incremental execution step and so runtime performance is cut
> back compared to what it could be.

Since you're using CPS, it seems like you have most of the infrastructure 
you need already. If you track source locations from parsing down to code
generation, you can place a source location into each frame as you allocate
it. Then, when you have an error, you can simply walk back down the
contination, emitting the source location of each frame. Since the
continuation represents the rest of the computation, you automatically get 
the exact execution context of the erroneous statement. 

The overhead for this is one word per frame, plus initializing it 
at each continuation allocation. This is probably acceptable unless
you are writing a really high-performance compiler. Also, tail-call
optimization will make the traceback only approximate, but this is
IME not a big deal in practice. 

Below is a small CPS-style lambda calculus interpreter that uses this 
idea to generate tracebacks of its evaluation. It's a treecode 
interpreter, but the ideas ought to carry over directly to bytecode too. 

-*-*-*-

type var = string
type pos = int

(* Expressions carry the source location with them *)

type expr =
  | Lit of pos * int
  | Var of pos * var
  | Fun of pos * var * expr
  | App of pos * expr * expr

(*
   Continuation frames have the position as an extra argument, so that
   the continuation as a whole contains the backtrace info. Primitive
   functions take the continuation as an extra argument so that they
   can generate a backtrace on an error.
*)

type denot =
  | Int of int
  | Closure of env * var * expr
  | Primop of (denot -> cont -> denot)
and frame =
  | KFun of pos * env * expr
  | KArg of pos * env * denot
and env = (var * denot) list
and cont = frame list

(*
   Here are the guts of the machinery to generate a backtrace. We
   need to extract a position from a frame and then just map over
   the whole continuation to get the backtrace.
*)

exception Eval_error of string * pos list

let framepos frame =
  match frame with
  | KFun(pos, _, _) -> pos
  | KArg(pos, _, _) -> pos

let backtrace k = List.map framepos k 

(* 
  We augment variable lookup so that failed lookups generate backtraces.
  If you check statically for unbound variables this is unneccesary.
*)

let bind v d r = (v,d) :: r

let lookup v r k =
  try
    List.assoc v r 
  with
    Not_found -> raise (Eval_error("Unbound variable " ^ v, backtrace k))


(*
   In the interpreter proper, the only place where a backtrace is 
   generated is when an argument is applied to a non-function. If an 
   if-statement is added, then that should be too. Likewise, if 
   exceptions are added then an uncaught exception should generate
   one also.
*)

let rec eval e r k =
  match e with
  | Lit(pos, n) ->
      next (Int n) k
  | Var(pos, v) ->
      next (lookup v r k) k 
  | Fun(pos, formal, body) ->
      next (Closure(r, formal, body)) k
  | App(pos, f, arg) ->
      eval f r (KFun(pos, r, arg) :: k)
and next d k =
  match k with
  | [] -> d
  | KFun(pos, r, arg) :: k' ->
      eval arg r k'
  | KArg(pos, r, func) :: k' ->
      (match func with
      | Int _ ->
          raise (Eval_error("Not a function!", backtrace k))
      | Closure(r, formal, body) ->
          eval body (bind formal d r) k'
      | Primop f ->
          next (f d k) k')
--
Neel Krishnaswami
neelk@cswcasa.com
-------------------
Bug reports: http://caml.inria.fr/bin/caml-bugs  FAQ: http://caml.inria.fr/FAQ/
To unsubscribe, mail caml-list-request@inria.fr  Archives: http://caml.inria.fr


^ permalink raw reply	[flat|nested] 11+ messages in thread
* Re: [Caml-list] Error Reporting
@ 2001-09-28 16:49 David McClain
  2001-10-01  0:52 ` John Prevost
  2001-10-01 14:17 ` Frank Atanassow
  0 siblings, 2 replies; 11+ messages in thread
From: David McClain @ 2001-09-28 16:49 UTC (permalink / raw)
  To: caml-list

Er... sorry, I really hosed up the CPS translation...

> There must be a past history, or otherwise you would not be able to
implement
> function application.

I must confess that I don't understand this statement...

Let's take a trivial example of a sequence of 3 "primitive" operations -- a
primitive operation being one which cannot make use of a continuation
argument.

Normal OCaml syntax for such a function would be:

let seqFn () =
    aFn ();
    bFn ();
    cFn ()

Now let's write what the compiler would translate this into....

let cPart () kont =
     cFn ();
     kont ()

let bPart () kont =
    bFn ();
    cPart () kont

let aPart () kont =
    aFn ();
    bPart () kont

The compiler returns the "aPart" closure as the toplevel result of compiling
the seqFn. The kont argument of the toplevel call, at runtime, represents
the continuation that should be jumped to after the entire seqFn equivalent
has executed. But continuations are always forward pointing links to program
history and never backward links. Without a stack and normal function
subroutine calls there is no place for execution history.

Here you see that all continuations are executed as tail jumps without
leaving an audit trail of history. Each subfunction stands alone and knows
nothing of the others. All any of them know is what they must do for their
part, and what continuation to jump to to finish up the program.

So now, if an error occurs in any of these inner closures, how will they be
able to report the chain of events leading to their execution?

But I suppose you are hinting that the compiler knows that all three of
these closures are contained in the outermost one and so some record of
activation history could be maintained by the compiler secretly plating
static linkage information into each sub-closure. Not a bad idea! It
certainly beats runtime computation of dynamic linkage and storing all that
in a history queue.

Thanks,

- DM



----- Original Message -----
From: "Frank Atanassow" <franka@cs.uu.nl>
To: "David McClain" <barabh@qwest.net>
Cc: <caml-list@inria.fr>
Sent: Friday, September 28, 2001 8:24 AM
Subject: Re: [Caml-list] Error Reporting


> David McClain wrote (on 27-09-01 13:36 -0700):
> > In a tail call, and all continuations are tail calls, there is no record
of
> > past history -- hence no trail of continuation frames. I can easily
report
> > the location of the present error, but there is no history to report.
>
> There must be a past history, or otherwise you would not be able to
implement
> function application.
>
> Let's make a distinction between continuations and tail calls. Really, it
> sounds like you are only _representing_ continuations in the source
language
> as tail calls in the target language. So in the target language, the two
are
> identified, but when you are compiling the source, you know which
> continuations are tail-recursive calls in the source language, and which
are
> only functional returns. In the code you emit, i.e., in the target
> language, you can thus distinguish these two types of continuations, and
when an
> error occurs, traverse the continuations which represent functional
returns,
> which should eventually lead to the prime mover, the entire program's
> continuation.
>
> --
> Frank Atanassow, Information & Computing Sciences, Utrecht University
> Padualaan 14, PO Box 80.089, 3508 TB Utrecht, Netherlands
> Tel +31 (030) 253-3261 Fax +31 (030) 251-379
> -------------------
> Bug reports: http://caml.inria.fr/bin/caml-bugs  FAQ:
http://caml.inria.fr/FAQ/
> To unsubscribe, mail caml-list-request@inria.fr  Archives:
http://caml.inria.fr
-------------------
Bug reports: http://caml.inria.fr/bin/caml-bugs  FAQ: http://caml.inria.fr/FAQ/
To unsubscribe, mail caml-list-request@inria.fr  Archives: http://caml.inria.fr


^ permalink raw reply	[flat|nested] 11+ messages in thread
* Re:  [Caml-list] Error Reporting
@ 2001-09-27 13:00 Damien Doligez
  0 siblings, 0 replies; 11+ messages in thread
From: Damien Doligez @ 2001-09-27 13:00 UTC (permalink / raw)
  To: barabh, caml-list

>From: "David McClain" <barabh@qwest.net>

>I am looking for some ideas on handling program error reporting for a
>compiler that is built largely on CPS. Since there are no stack frames to
>crawl for traceback, I have a first cut based on keeping a finite length
>queue of last visited closures.

It seems to me that the current continuation in a CPS program should
contain the same information as the stack in a stack-based program.
You have the problem of losing information on a tail-call in both
systems, for example.  So you should be able to do as well as a stack
crawl with a "continuation crawl".  Of course, this will depend on the
data structure used to represent continuations, and if you use normal
closures for that, you will be compiler-dependent, but again this is
no worse than the stack-based case.

Or am I missing something ?

-- Damien
-------------------
Bug reports: http://caml.inria.fr/bin/caml-bugs  FAQ: http://caml.inria.fr/FAQ/
To unsubscribe, mail caml-list-request@inria.fr  Archives: http://caml.inria.fr


^ permalink raw reply	[flat|nested] 11+ messages in thread
* [Caml-list] Error Reporting
@ 2001-09-26 22:55 David McClain
  0 siblings, 0 replies; 11+ messages in thread
From: David McClain @ 2001-09-26 22:55 UTC (permalink / raw)
  To: caml-list

Hi,

I am looking for some ideas on handling program error reporting for a
compiler that is built largely on CPS. Since there are no stack frames to
crawl for traceback, I have a first cut based on keeping a finite length
queue of last visited closures. But this becomes problematic when there are
circularities in the code, i.e., recursion. Also, I think I have way too
much error tracking going on at each incremental execution step and so
runtime performance is cut back compared to what it could be.

This compiler is written in OCaml and so there really are stack frames in a
few places.

Any ideas?

- David McClain, Sr. Scientist, Raytheon Systems Co., Tucson, AZ
-------------------
Bug reports: http://caml.inria.fr/bin/caml-bugs  FAQ: http://caml.inria.fr/FAQ/
To unsubscribe, mail caml-list-request@inria.fr  Archives: http://caml.inria.fr


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2001-10-01 15:53 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2001-09-27 13:32 [Caml-list] Error Reporting Krishnaswami, Neel
2001-09-27 20:36 ` David McClain
2001-09-28 15:24   ` Frank Atanassow
2001-09-28 16:41     ` David McClain
2001-09-27 22:34 ` David McClain
  -- strict thread matches above, loose matches on Subject: below --
2001-09-28 16:49 David McClain
2001-10-01  0:52 ` John Prevost
2001-10-01 14:17 ` Frank Atanassow
2001-10-01 15:44   ` David McClain
2001-09-27 13:00 Damien Doligez
2001-09-26 22:55 David McClain

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).