Welcome to the main channel on the development of MoarVM, a virtual machine for NQP and Rakudo (moarvm.org). This channel is being logged for historical purposes.
Set by lizmat on 24 May 2021.
Nicholas good *, #moarvm 08:39
lizmat Nicholas o/ 09:43
jnthnwrthngtn moarning o/ 10:29
Nicholas \o 10:30
nine moarning goodness!
jnthnwrthngtn: did you follow my investigation yesterday evening? 10:34
jnthnwrthngtn nine: Not closely. 10:37
Bit short on time for Raku things this week, alas. Plus I've got a sore throat and other cold-y symptoms. :/ 10:38
nine jnthnwrthngtn: oh, sad to hear that :/ Hope you'll get better soon 10:39
Nicholas :/
jnthnwrthngtn On the upside, I do now have my permanent residence permit at last. :)
Yeah, I'm hoping it's just a boring "was walking in cold weather, caught a cold" thing rather than anything more sinister. 10:40
Nicholas So you never need to visit the wrong side of Prague again? :-)
nine jnthnwrthngtn: the one question on which I'd really appreciate input is: return_o calls MVM_try_return which finds an exit handler on the current frame. It then calls MVM_frame_dispatch_from_c to run this exit handler. MVM_frame_dispatch_from_c set's the caller's return address to current cur_op. But that was already advanced, so it actually points right at the end of the bytecode. So when we return from the 10:41
jnthnwrthngtn Well, not for 10 years or so.
nine exit handler the runloop exits the bytecode. How is this supposed to work?
Nicholas in 10 years we might even have self driving taxis... 10:42
jnthnwrthngtn nine: At the point we call the exit handler block the frame whose exit handler it is is still on the stack, since dynamics should be in scope. Ahead of the dispatch_from_c it sets a special return handler for remove_after_handler. When we finish running the LEAVE block, we return_o from it. That frame is removed from the callstack, we then also find the special return handler, which in turn triggers removal 10:45
of one further frame.
The return address that is written in dispatch_from_c should never be relevant in so far as we never truly return into the frame in question, but the special return handler removes it also. 10:46
This is how it's meant to work and how the code looks, but I'm guessing if you're asking, it can somehow go wrong. 10:47
fwiw, I'd planned to re-work this lot so that a) we install code ahead of the return_o to run the exit handler, and b) static frames carry an "exit handler address" so we can also run it during unwind. 10:48
(So that in the non-exceptional case we can do stuff like inlning LEAVE blocks, or at least get rid of the massive amount of indirection and overhead imposed today)
nine jnthnwrthngtn: ok, thanks, that helps a lot! So I'll have to investigate why the special return handler (which definitely is on the stack) doesn't unwind far enough
jnthnwrthngtn Well, one thing that gets a bit icky: the special return handler is triggered from in unwind frame, which then in turn calls unwind_frame again 10:49
nine I noticed, yes
jnthnwrthngtn This should not matter, because after the special unwind there would be a frame on the stack top 10:50
nine I'm very much looking forward to knowing the explanation. Most of all because of that peculiar behaviour: MVM_SPESH_BLOCKING=1 makes it fail reliably but even with MVM_SPESH_LIMIT=1. So somehow there's a bit of spesh in there but not because of actually speshed frames
jnthnwrthngtn And that's the termination condition for the loop.
Oh. That's weird. 10:51
And disabling spesh fixes it? Does it need blocking to be on, or does LIMIT=1 block it?
iirc disabling spesh also disables the optimization that sees us allocate frames right off on the heap if we see they tend to get promoted 10:52
nine So far I haven't seen a failure with spesh disabled.
I cought it with `MVM_SPESH_LIMIT=1 MVM_SPESH_BLOCKING=1 rr record rakudo` and confirmed that none of the involved frames are speshed
Actually it looks like it fails reliably even without MVM_SPESH_BLOCKING=1 if MVM_SPESH_LIMIT=1 is present 10:54
So really either env var will do
Which usually means "memory layout". But in any case, I'll single step through the sequence in rr. That should shine some light 10:56
nine jnthnwrthngtn: ah, I think I understand what's going on. Not the spesh part, but the rest: it fails to clean up that frame, because the special return handler call stack entry is not followed directly by a frame entry. There's a region entry in between because the special return entry didn't fit into the previous region anymore 14:30
nine When the frame is right on the top of the stack, the do/while loop cleans it up. But since it isn't, it cleans up the region and exits the loop when it finds that the new frame is a bytecode frame. 14:31
A crude hack would be adding a flag to unwind_frame that counts if a frame was actually removed and only then exits the loop 14:32
jnthnwrthngtn nine: The spesh part is probably what I mentioned earlier: with spesh disabled I think we always heap-promote frames, not eagerly put them on the heap if they tend to get promoted 14:40
And thus it results in different sizes of things on the stack and creates this boundary sitaution.
I'm still slightly confused how what you're describing happens (but I can imagine it does...) 14:41
In that the nested call should mean we are entering unwind frame once for each of the 2 frames we expect to remove
And either what's on the top is a frame (and we exit it) or is not (and we work towards there being a frame on the top) 14:42
oh, hang on, I think I see it 14:43
jnthnwrthngtn Yeah. Argh. Indeed. unwind_frame should only ever really be called when there is a frame atop of the call stack. It should probably have an assertion that this is the case. 14:44
So perhaps the special return handler should also drop any continuation tag or region start records too before invoking the special return handler, *or* we should have an unwind_frame_from_anything that first clears up any region start or continuation tag records 14:46
nine I guess the most interesting open question is how to avoid duplicating a lot of code while keeping runtime cost at 0. I'll play around with the code a bit and see if something sticks 16:27
Well, runtime cost for non-special-return cases. There's no free lunch for the latter 16:29
jnthnwrthngtn I'd first put in the assertion that, at the entry to unwind_frame, the top-most thing is a frame 16:32
nine Apparently it's quite common for a MVM_CALLSTACK_RECORD_DISPATCH_RECORDED to be on stack_top in unwind_frame 16:33
jnthnwrthngtn That's a bit odd.
Maybe I forget, but I didn't think we used unwind_frame in the situation that we had a dispatch program that produced a non-bytecode result 16:34
nine This happens even in the first call during NQP's build in a garden variety return_o 16:36
When returning from a frame that doesn't even do any dispatch? 16:40
Ah, man, what a stupid mistake. Maybe just not put that assertion into the loop? 16:51
jnthnwrthngtn :D 16:52
Typical Fri...oh.
nine Well, I could take it as a sign, that hacking dinner dinner is more appropriate right now than hacking code. But then, do I want to use a sharp knife in my condition? :D 16:53
Anyway, when put into the right place, the assertion seems to catch this situation 16:54