On 31/01/2014, at 11:49 AM, john skaller wrote:

> There's a bug in corouitines--01.flx I can't figure out.

Oooo .. I have now. This is what I'm going to call a Srean bug.

There's no bug in the compiler. It's not clear even if there
is a bug in the test case.

So to explain I'm going to backstep a bit. In Felix we can
use heap allocated stack frames. You are allowed to return
a pointer to a local variable because if you do, the GC simply
won't delete the frame (the compiler guesses when it is safe
to put the frame on the machine stack, never the case if you
can return a pointer to local variable!)

There's a problem here though. Unlike a language using
boxed types, a pointer to a single local variable means the
GC keeps the WHOLE frame.

And that frame contains the return address, i.e a pointer to the
calling frame (as well as a pointer to each of the most recent
ascendent frames and the thread frame).

This means if you do a long recursion and then return a pointer
to the 1000'th frame what happens? Well if you pointed at a pointer
which pointer into its callers frame the whole 1000 frames are
reachable. But what if not?

Well the return address means there are still pointers back
through the whole 1000 frames. So if the frame has returned
it makes sense to zero out the return address, so you can
only return once.

Sounds reasonable, except that with non-local gotos you CAN
in fact jump into a frame that has already returned. If you do
that and try to return again, then either you will return to the frame
as before, or exit Felix if the return address is 0 (because that's
the code for "program end"). If we NULL out the return address
we're confusing the normal program end with a hack which is just
meant to stop the whole call chain being held in memory.
But keeping the old return address is nonsense too!
In fact it will go to the caller frame but run that from from
the program counter of that frame, and that will be whatever it was
set to last during the previous execution. It won't be anywhere sane.

So now, an even worse case: a computed goto. In principle this
is nonsense unless it is executed BY the current frame
and is a jump TO the current frame.

So now look at the code:

// silly test of computed goto
proc fred () {
  println$ "Hello";
  var x = 5;
lab1:>
   println$ "Next " + x.str;
   --x;
   var labvar = (label_address lab1);
   if x > 0 do
     goto-indirect labvar;
   done
   noinline proc joe () {
      var lv = label_address lab1;
      x = -2;
      goto-indirect lv;
   }
   if x >= 0 call joe;
}

fred;

println$ "FRED HAS RUN";


This was actually just some crap I hacked up to see if some
cases of computed jumps and addressing labels worked.

But the results are indeterminate! If I set inlining to a low value, fred isn't 
inlined
into the main procedure. Now look at the last statement in fred and you will
see it is a tail call. This is optimised by setting joe's return address to 
freds
(the mainline), and zeroing out fred's return address, since a tail call implies
you cannot re-enter the caller, fred.

Only, that's precisely what joe does: it jumps into fred. So when fred
reaches the end it  returns 0 which ends the program. It's an accident,
the 0 ends the program and is also used just to NULL out the return
address for GC purposes.

However leaving the address alone will not solve the problem in general
even though it would in this case, and even if you ignore that it might
cause a lot of heap to remain reachable when it shouldn't.

No, the problem is a very high level semantics issue.
I see no way to properly answer the question: what should happen
if you re-enter a procedure that has already returned? What should
happen when and if it tries to return again?
If it aborts, why should it do something first, why no abort
immediately? [What I actually mean is, if its undefined behaviour
to return again, why would the behaviour be defined at 
an point prior to doing that?] if it doesn't abort, what can you say
about what happens when the return causes another procedure
to be re-entered at the pointer where it just happened to last
record the program counter?

With coroutines in a pair like the second part of the test case
where control is zig-zaging, its well defined because we're
just "temporarily suspending a thread of control" to do something
else and then later "continuing on".

There is another example where re-entry seems right: if you have
some "tail code" which is not normally executed then jumping
to it non-locally as a way of throwing an exception make sense,
at least until the handler has finished.

An interesting conundrum!


--
john skaller
skal...@users.sourceforge.net
http://felix-lang.org




------------------------------------------------------------------------------
WatchGuard Dimension instantly turns raw network data into actionable 
security intelligence. It gives you real-time visual feedback on key
security issues and trends.  Skip the complicated setup - simply import
a virtual appliance and go from zero to informed in seconds.
http://pubads.g.doubleclick.net/gampad/clk?id=123612991&iu=/4140/ostg.clktrk
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to