Copyright © 1998, 2001 by Ian Horswill, see comment within this file.
Debugging GRL code
Debugging GRL code
Here are a few helpful hints for debugging your GRL code.
Errors generated by Scheme
There are mostly errors that occur at run-time, but they can also occur
during compilation if either the compiler is broken, or it tries to constant
fold a bad expression. For example, if you say (define-signal foo (/
7 0)), the compiler will realize that foo is a constant and
try to compute its value at compile time by doing the division. This will,
of course, break.
- Error: Exception
- This generally means you called something with arguments of the wrong
type. If two lines appear after the error message, then the next line
tells you what argument it didn't like and the line after that tells you the
expression it was trying to evaluate. If there's only one line, then
it was the expression it was trying to evaluate.
- Error: Undefined variable
- Just what it sounds like: you referenced a variable you hadn't
defined. The next line tells you what the variable is and the line
after that tells you what package it was supposed to be in. Note that
in some cases, this can be caused by arranging the definitions in a source
file in the wrong order. In general, you need to place definitions of
signals before their uses.
- Error: Rational division by zero
- Your program divided by zero. The next line tells you what number it
tried to
divide by zero.
- Error: Stack overflow
- This is a very rare error to get in GRL code, since it isn't recursive,
but it means your program somehow went into an infinite recursion.
- Interrupt: Keyboard
- You typed control-C. Type
,reset or ,pop
to get out of the debugger unless you want to probe the stack.
- Interrupt: Post-gc
- Scheme ran out of memory. This usually means something bad is
happening. Either you wrote an infinite recursion, wrote an infinite
loop that kept allocating a bigger and bigger list, or possibly scheme
itself got confused trying to call an error handler that generated an error
itself. There usually isn't a lot you can do if you get this.
Type "
,reset" to get out of the error and hope that
scheme is at least able to clean up the mess.
- Warning: Invalid arguments
- This is a type error caught by the scheme compiler. The next line is the
expression being compiled and the other lines are the types the procedure
expects and the types it's being passed, respectively.
Common compilation errors
Most compilation errors are straight-forward. Here's a list of all but
the most obscure or most obvious ones:
Errors you get while typing in or loading your code
- Bad signal declaration
- This means that your signal expression had some extra garbage at the end,
for example, if you say something like:
(define-signal foo (+ a b) bla bla bla)
it will complain that bla wasn't a valid declaration to
add to a signal expression.
- Function argument x to map-signals must be a procedure, signal-procedure, or transducer
- This means you said something like
(define-signal foo (x y))
where x wasn't something it knew how to treat as a
procedure. With very few exceptions, the operator in a signal
expression (x, in this case) has to be a scheme procedure, a
signal procedure, or a transducer.
- One of this primitive procedure's arguments is invalid
- You said something like
(+ sin 7), which is a type error
because sin is a procedure, not a number, and the system was
able to catch it at load time rather than at compile time.
- Unknown signal property x in declaration of signal
- This means that your signal expression had some extra garbage at the end,
for example, if you say something like:
(define-signal foo (+ a b)
(bla bleep))
it will complain that bla wasn't a valid signal
property.
-
Type inference errors
- Consequent and alternate branches of if have different types
- You said something like
(if (< a b) 1 #f), where the
"then" and "else" parts have different types. You
can do that in scheme but not in GRL.
- Group type must have x field, but doesn't
- That means your code tried to access the x component of a signal
group but the group didn't have a component by that name. Look at the
compiler's subgoal stack to figure out what it had been expanding at the
time. That will tell you what procedure is broken.
- Invalid argument type(s)
- This means you applied a procedure or transducer to arguments of the wrong
types. The error message shows you what the types of the arguments
were, in order, along with what types it was expecting.
- Need to know the type of x, but can't determine it
- This is actually generated by the partial evaluator when it's trying to
optimize the intermediate code. It generally means that the
intermediate code variable x is defined in terms of some foreign
procedure (generally from a source signal or from a transducer body) that
the compiler hasn't heard of. The solution is to look at the
compiler's subgoal stack, find the procedure, and use
define-external
to tell the partial evaluator what it returns.
- Not a numeric type
Not a Boolean type
Not an integer
Not a logical type
Not a list type
Not a vector type
Not an array type
- This means you did something like
(+ #f 7) where you apply a
procedure to a nonsensical type. It's essentially the same kind of
error as invalid argument types(s), except that hte compiler knew a little
more about what was going on.
- Unable to infer type for a cyclic graph
- This means you've defined a signal in terms of itself and so the compiler
can't figure out what it's type is. For example, if you define
a
to be b+1 and define b to be a+c-7,
the compiler won't reject the code; it assumes that you mean that when
updating, the new value of a should be set to the current value
of b+1, and the new value of b should be set to the current
value of a+c-7, but it isn't smart enough to determine whether a
and b are floats or integers. Since it's rare for someone
to mean to define a signal in terms of itself, the solution to this problem
is usually to find out why the signal mentioned in the error message is
cyclic and fix it so that it isn't cyclic. However, sometimes you
really mean to make it cyclic. In that case, you can fix it by adding
either a type declaration, or preferably, an initial-value
declaration, which both lets the compiler know the signal's type and also
tells the compiler what to assume for the initial value of the signal.
- Unknown type
- You shouldn't get this unless you're doing something fancy. It means
that you used a constant someplace in your code that it couldn't determine
the type of (e.g. some funky record structure).
Expansion errors
- Argument to group selector must be a signal or list of signals
Not a signal group
- Your code tried to take a component (group element) of a signal that
wasn't a group to begin with.
- Attempting to extract group component named x from a non-signal!
Tried to select the x component of a signal, but it's not a group!
- Your code tried to get a component of a group (either with
select,
or an accessor like x-of, rotate-of, etc.) but it
wasn't a group
- Calling signal as function with no function binding
- This means your code tried to call a signal on another signal, as if the
first signal were a function. Don't do that, silly. The
"with no function binding" clause is there because you can in fact
overload signals to behave as functions (using the
as-function
declaration). This error message is only printed when you don't do the
overloading but try to call the function as a signal anyway.
- No handler for signal type
- This means a signal procedure had a
typecase expression in it
and the signal it was looking at had a type that didn't match any of the
clauses in the typecase expression. Usually, it means
that you're passing something of the wrong type into a signal
procedure. The offending signal is given as part of the error report.
- Not a literal value
- This means that either the argument of some procedure or signal procedure
needed to be an actual number rather than an expression, but wasn't.
- Signal has no property named x
- This means that either your code or some code you called expected one of
its arguments to have some property defined in its property list and it
didn't. This usually means you've passed the wrong kind of thing in as
an argument. For example, you can get this if you try to call
something from within a plan that isn't really a plan or action.
- This signal group has no x component
- Your code tried to extract a component from a group that doesn't have a
component by that name. For example, if you call
x-of on
an rt-vector.
Other random errors
- Beta reduction (i.e. lambda inlining) with arguments not implemented
- This means you has a source signal or transducer body that said something
like
((foo bar) baz). You can do that in scheme, but GRL
only allows it in very restricted cases. However, most users never
even try to write that anyway, so getting this error usually means that you
had an extra set of parentheses around an expression in a transducer body,
or else that you had a typo in the name of a macro, e.g. typing "ket"
instead of "let", so that the compiler misinterprets
the list of variables and values as a procedure call.
- Can't reify signal group
- This is hard to get, but it usually means you passed a group as an
argument to a transducer or plan. The compiler can't handle that.
- Found signal in object code with no location assigned!
- This is usually a bug in a plan, plan macro, or transducer. It means
that a plan or transducer expanded into some code that referenced a signal
that (a) wasn't one of its arguments (don't ask how to do this ...) and (b)
wasn't used elsewhere in the compilation. If you get tell Ian and
he'll help you track it down.
- GRL doesn't know how to compile x
Unknown procedure object
- This means you compiled a signal expression like
(proc a b c)
where proc was a real scheme procedure but it didn't happen to
be a scheme procedure that the GRL compiler knew how to compile references
to. Use define-primitive to explain the procedure to GRL.
- x isn't a real procedure
- There are a bunch of primitives that GRL knows how to compile that aren't
real scheme procedures that it makes sense to call. These are mostly
the array primitives. Scheme doesn't support multidimensional arrays,
so you can't actually call these primitives from scheme.
- Invalid variable reference
- This is an error message generated by Scheme, not by the GRL
compiler. It means that you used the name of a macro or other special
form as the argument to a procedure rather than calling it properly.
For example, if you say
(+ lambda 1). This generally
means one of two things:
- In scheme,
if, and, not, and or
are all macros. You can't pass them to procedures like map.
However, we've done some special magic in GRL so that they can be used
in signal expressions as normal values. So getting this error
usually means that you used a signal expression where you were really
supposed to put a scheme expression. The most common example of
this is the body of define-signal-procedure, which wants you to type
scheme code rather than GRL code for the body. If you want to give
GRL code for the body, use define-signal.
- Sometimes you've forgotten to open a package that exports a
macro. That means that when you call the macro, the compiler
thinks its a procedure and doesn't parse it specially. Depending
on the syntax of that macro, its arguments may look funny to the
compiler and cause the invalid variable reference error.
Understanding more obscure compilation errors
When the compiler finds a problem with your code, it prints an error message
and drops you in the debugger. In addition to printing the error itself,
the compiler will try to print a trace of what it was trying to do at the time,
which is helpful for figuring out where the error is. The phases of the
compilation process are:
- Expansion
The compiler calls all your signal procedures. C++ programmers can
think of this as in-lining functions. The compiler recursively expands
your program until there's nothing left to inline.
- Collecting accumulator inputs
If you don't know what accumulators are, then ignore this. You don't
have any. If you do, then this is where the compiler searches all the
signals it's expanded to find the inputs for the accumulators in your code
- Type inference
The compiler then determines the types of all of your signals. If
you have two signals defined in terms of one another, it may complain
because it can't determine their types. If so, it will ask you to add
a type declaration or an initial value declaration to disambiguate the
type. Note that type inference can also occur on selected signals
during the expansion phase.
- Analysis
The compiler figures out which signals can be omitted because they aren't
really contributing to the final output, then decides on the best order in
which to update the signals at run-time. Finally, it figures out which
signals are really constant values and which are only used once so they can
be further inlined.
- Reification
This is sort of "un-evaluation", in which the intermediate code
for updating each signal gets compiled. Intermediate code is
essentially scheme code that never recurses or allocates memory.
Note: This step is really mis-named. It comes from the name of
something in the scheme48 compiler, but it's a confusing and misapplied term
here. Sorry.
- Partial evaluation
Then the compiler takes the intermediate code and optimizes it. For
example, it notices expressions like (+ 1 (* a 0) 3 b) and
changes them into (+ 4 b). Sometimes this involves some
more type inference. However, this time it's computing the values of
intermediate code variables rather than of signals.
- Final code generation
The compiler passes the optimized intermediate code off to whatever back-end
you're using. The back-end is basically just a pretty-printer that
transliterates the intermediate code into C++, BASIC, or whatever other
language you're using.
Debugging control systems is always hard because you can't exactly
single-step them in the debugger - they have to keep running all the time.