This is the mail archive of the guile@cygnus.com mailing list for the guile project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Another note on that optimiser thingy


Well it's funny that everyone is talking about bugs caused
by the optimiser -- I just found where I was being bitten by
it too, probably bitten for some time without realising exactly
where the problem was coming from. The error:

insert_test.scm:38:5: In procedure point+ in expression (point+ (random-error-point) (list # # ...)):
insert_test.scm:38:5: Wrong type argument in position 1: (e{@e{@-01 0.0050657 0.0244)


Doesn't explain much except that you can see something that looks like
memory corruption and it is happening to what should be a floating point
value. What finally turned out to be the cause:


{
	int c;
	long pos = 0;
	SCM result;

	SCM tok_buf = scm_makstr( 100L, 0 );

	while( 1 )
	{
		switch( c = scm_getc( port ))
		{
			/* lots of cases removed */
			default:
			{
				char *p = SCM_CHARS( tok_buf );

				scm_ungetc( c, port );
				result = scm_istring2number( p, pos, 10L );
				if( SCM_BOOL_F != result ) return( result );
				return( SCM_CAR( scm_intern( p, pos )));
			}
		}
	}
}


As you can see, *p takes the internal memory of tok_buf which is
then not referred to before the next return() statement.
This leaves the optimiser free to throw away tok_buff and scm_istring2number
can sometimes activate garbage collection.

The quick solution is to put a volatile in the declaration of
tok_buf, another (better?) solution is to avoid scm_istring2number()
and scm_intern(), preferring something that works directly on the
SCM value. Thus, the following works without the volatile declaration:

	default:
	{
		char *p = SCM_CHARS( tok_buf );
		SCM sub = scm_make_shared_substring( tok_buf, SCM_MAKINUM( 0 ),
							 SCM_MAKINUM( pos ));

		scm_ungetc( c, port );
		result = scm_string_to_number( sub, SCM_UNDEFINED );
		if( SCM_BOOL_F != result ) return( result );
		return( scm_string_to_symbol( sub ));
	}

The reason is that even when tok_buf is thrown away, sub keeps the
memory usable and sub hangs in there right till the end. The implications
of this are logical enough once you have gone through it a few times but
it is going to become a big trap to newbies unless it gets well documented.

As an aside, it is interesting to note that the libguile code for
scm_make_shared_substring() uses the optimiser-defeating trick for
exactly the same reason, so using this function is a way of passing
the buck. On the other hand scm_string_to_symbol() does NOT use the
optimiser protection nor does it declare anything volatile.
Apparently, the only reason that it works is because function arguments
tend to be on the stack (OK, I'm on 386-linux system, they are ALWAYS
on the stack) but this is not an absolute truth -- I'll give the UltraPenguin
a try sometime and see if I can break it.

Something for the TODO list I guess is to draw a line around which
functions can get you into trouble (e.g. SCM_CHARS(), SCM_ROCHARS(), etc)
and maybe start thinking about the sort of cases those functions have
been used in.

One big one is probably SMOB code that takes a SCM value which is
the SMOB and first thing it does is extract a pointer to a C struct.
>From there on in, all the work is done with the C struct and the
SCM pointer is probably left untouched in 9/10 cases. Most of these
are safe because the SCM value is a function argument so it's on the
stack. Better than that, most SMOBs aren't created just to be used
once and destroyed so something else usually points to them.

Neither of these protections are completely trustworthy (unless
you always use a 386 and pass your args on the stack).

	- Tel