Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed interpreted program. #2

Merged
merged 4 commits into from
Apr 6, 2017
Merged

Fixed interpreted program. #2

merged 4 commits into from
Apr 6, 2017

Conversation

jaheba
Copy link
Contributor

@jaheba jaheba commented Mar 23, 2017

Not cleaned up, but seems to work :)

@ltratt
Copy link
Contributor

ltratt commented Mar 23, 2017

Once Jasper's tidies this up a bit, it should be ready for review by @vext01 and me.

@jaheba jaheba requested a review from ltratt March 23, 2017 17:34
@vext01
Copy link

vext01 commented Mar 24, 2017

I don't have any context. Is this is just an interpreter written in rust? What's GreenMT? Is it a re-naming of grass?

@jaheba
Copy link
Contributor Author

jaheba commented Mar 24, 2017

@vext01 Let me add a README. In short it's a tracer.

@ltratt
Copy link
Contributor

ltratt commented Mar 24, 2017

Jasper, are you saying this is now ready for review or ...?

@jaheba
Copy link
Contributor Author

jaheba commented Mar 24, 2017

Well, yes.

@ltratt
Copy link
Contributor

ltratt commented Mar 25, 2017

Before Edd and I do a review, can we get the formatting within the project consistent? I don't mind too much what it is, so long as it's consistent (e.g. blank lines between structs/functions). When it's inconsistent, it's a distraction that prevents reviewers concentrating on the important stuff.

@jaheba
Copy link
Contributor Author

jaheba commented Mar 25, 2017

Will do, let me see if rustfmt has some option for that. What style guide are you using for lrtable?

@ltratt
Copy link
Contributor

ltratt commented Mar 25, 2017

I use "Laurie's patented style". At this point, it doesn't matter too much provided you're a) not doing something no-one else has ever done b) it's consistent within the project.

vext01
vext01 previously requested changes Mar 27, 2017
Copy link

@vext01 vext01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of small comments.

I didn't see the call inlining code in my review. Can you point it out?

@@ -0,0 +1,24 @@
# daly
Simple VM for a Dyon--subset
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to dyon website? I've never heard of dyon, so it's likely others haven't either.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it just a VM, or a VM and a tracer. A bullet point below leas me to think the latter.

### Optimisations

**Inlining**
Inlining of function calls is performed. Necessary steps for deoptimisation can be found in `tracerunner::Runner::recover`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deoptimisation for inlining would be pulling an inlined function back out into its own unit with an activation record etc? Why would you need to do this? Are you inlining specialised versions of functions perhaps?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put another way, why would you want to "un-inline" a call?

Maybe this is a great idea for some reason, but it's not a conventional approach (AFAIK).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me give you a (somewhat artificial) example. Imagine you've got a function f which inspects the stack. If you inline f, you then have to deopt so that it can inspect the stack; you'd be better off not actually inlining it but you might only realise that after you've inlined it.

That said, in general, you're right. But I think the point Jasper is making is not about uninlining, so much as it's about "we called f, a guard failed, so we have to blackhole".

}


// impl<'a> From<&'a Instruction> for TraceInstruction {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code.

use TraceInstruction as TI;

match instr {
I::Add => TI::Add,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the spacing here implying some kind of grouping? If so, add a comment, if not kill the whitespace?

@@ -1,19 +1,61 @@

#[macro_use] extern crate maplit;
// btreemap! macro
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is actually a comment, not commented code

maplit enables using the btreemap! macro

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, sorry.

src/main.rs Outdated

Clone => (),
// XXX
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand on this XXX?


pub struct Runner<'a, 'b: 'a> {
pub trace: &'a [TraceInstruction],
pub stack: Vec<Value>,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the stack be a fixed size array? I'm not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It certainly could have a reserved capacity at least.


info!("TEXEC: {:?}", instr);

match *instr {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the traces do the same as the interpreter for now? Let's add a comment?

}
}

// XXX: return None guard succeeds
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to fix that here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can actually be removed. guard used to return Option<usize>.

/// the following things have to be recovered
/// * stack-frames (call-frames)
/// * value stack (essentially bool which caused guard to fail)
fn recover(&mut self, guard: &Guard) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is blackholing. I was expecting this part to be much harder. Does this implementation have limitations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I guess it gets harder if one adds more optimisations -- each added optimisation possible requires some de-optimisation steps. I wonder what had to changed, if we allow references to stack variables.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know much about black-holing, so you are kinda on your own here ;)

@vext01
Copy link

vext01 commented Mar 27, 2017

Added more comments.

Can you point out where calls are being inlined. I can't see where this happens.

@jaheba
Copy link
Contributor Author

jaheba commented Mar 27, 2017

Inlining actually happens in multiple places.

Basically inlining just means, that stackframes of called functions are integrated into the one and only frame. Thus load and store instructions have to modified to account for the new local positions. Similarly arguments are stored into locals.

When calling a function during tracing, we have to reserve some space for the local variables and store recovery information for guards.

I think I should add some comments about that in the code.

@vext01
Copy link

vext01 commented Mar 27, 2017

Yes, it needs to be clearer. Usually you'd let tracing follow the function call (instead of inserting a call into the trace) and then let the trace optimiser remove the (now redundant) calling convention code (like you say, the locals of the callee may as well be locals of the caller). But I've never seem a VM deoptimise (un-inline) a function which a comment alludes to in the README.

@ltratt
Copy link
Contributor

ltratt commented Mar 27, 2017

@jaheba So I suggest you address Edd's comments (however long that takes), and then I'll have a go. Sound OK?

@jaheba
Copy link
Contributor Author

jaheba commented Mar 30, 2017

I'm on it!

@jaheba
Copy link
Contributor Author

jaheba commented Apr 4, 2017

@ltratt I think the code is now in a cleaner state. Maybe have a quick look, what I should address before you do the full review?

@ltratt
Copy link
Contributor

ltratt commented Apr 5, 2017

Oops, I think you accidentally committed your Cargo.lock file?

@jaheba
Copy link
Contributor Author

jaheba commented Apr 5, 2017 via email

@ltratt
Copy link
Contributor

ltratt commented Apr 5, 2017

Yuck :( I don't think this is very useful for early-stages programs, as it just clogs up the diffs. But I'm not going to argue against it.

let mut locals = TraceDataAllocator::new();
locals.alloc(instr.func.args_count + instr.func.locals_count);

let mut next = instr.clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to clone instructions all the time? Is this a restriction imposed by the borrow checker or ... ?

src/main.rs Outdated
@@ -192,167 +185,190 @@ impl<'a> Interpreter<'a> {
ArrayGet => self.do_array_get(),

Call(ref target) => {
let new_func = self.module.funcs.get(target).unwrap();
let mut frame = CallFrame::for_fn(new_func, (func, pc));
let new_func = self.module.funcs[target].clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eek, cloning the function is pretty heavyweight isn't it, given that we don't appear to be mutating it?

}

call_tree = call_tree.push(FrameInfo {
func: new_func.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this cloning the clone?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the first clone.

/// Thus, we have to reconstruct all missing callframes, before the
/// the interpreter can gain back control.
/// Second, we also have to consider the frame where the loop resides
/// in, since state might has also has changed there.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"has also" -> "have also"

let frames = guard.frame.walk().collect::<Vec<_>>();

// why .rev()? We rebuild the stack of frames, so we have to start with
// the bottom most.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you mean the topmost frame?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is topmost the latest item which was pushed onto the stack?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically "topmost" means "frame at the start of the stack" (and so "bottommost" means "frame at the end of the stack"). In retrospect, I'm not sure if my comment was correct or not as to the order in which you're doing things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to talk about a concrete example.

s = Stack()
s.push(1)
s.push(2)
s.push(3)

In my view, 1 is on the bottom of the stack, so it is the bottom-most value. The top of the stack is the last value added, in this case 3. What I want to say in the comment is that we have to start with 1 to rebuild the stack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, maybe we should use "most" and "least" "recent" for clarity.

fn recover(&mut self, guard: &Guard) {
// remove the last callframe of the Interpreter
// it gets replaced with our updated version
self.interp.frames.pop().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised that we can discard the current frame entirely. I assumed (maybe wrongly!) that it would have locals (etc.) which would need to be copied in the to-be-created frames?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The locals of the frame are copied into the trace-locals in Runner::new. The locals are restored in the first iteration of the following loop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but won't the locals in the frame have been (potentially) changed, meaning they no longer matched those stored in the Runner?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, only mutable references or a concurrent actor could have changed something in the original frame. Every other modification happens within the trace's locals.


// 2. fill it up with locals
for idx in 0..frame.locals.len() {
frame.locals[idx] = self.locals[frame_info.offset + idx].clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure how we can get the right version of all the locals from other frames? Won't some have been updated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand your question. Unless we have concurrent execution, I believe we should be fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to the comment about "can we pop the frame", so let's consider the topic in that conversation.

src/traits.rs Outdated
@@ -0,0 +1,29 @@

pub mod vec {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blank line.

@ltratt
Copy link
Contributor

ltratt commented Apr 5, 2017

OK, I've made various comments. I think we could really do with some more tests, particularly to really test blackholing. I'm also curious if we could reduce the cloneing, which looks like a bit of a performance hit (not that we care about performance at the moment, as such, but it might suggest that we haven't got quite the right architecture, which we do care about).

@jaheba
Copy link
Contributor Author

jaheba commented Apr 5, 2017

Yuck :( I don't think this is very useful for early-stages programs, as it just clogs up the diffs. But I'm not going to argue against it.

Nothing I feel strongly about. Should I just remove the Cargo.lock again?

@ltratt
Copy link
Contributor

ltratt commented Apr 5, 2017

Up to you. I don't feel strongly about it, I guess, provided that it doesn't get updated every 5 minutes.

@jaheba
Copy link
Contributor Author

jaheba commented Apr 5, 2017

Alright, then I'll let it in and we see how it goes.

@jaheba
Copy link
Contributor Author

jaheba commented Apr 5, 2017

I'm also curious if we could reduce the cloneing, which looks like a bit of a performance hit (not that we care about performance at the moment, as such, but it might suggest that we haven't got quite the right architecture, which we do care about).

I think, some clones are just unnecessary and others could be avoided. Also, since I've opted for using Rcs all over the place, it makes cloneing mandatory.

@ltratt
Copy link
Contributor

ltratt commented Apr 5, 2017

Ah, so some of the clones are Rcs. I didn't realise that (damn type inference!).

@jaheba
Copy link
Contributor Author

jaheba commented Apr 5, 2017

One thing I'm a bit unsure about is how to handle new traits, which I implement for common structs. In my case, I implement some methods on top of Vec. To make this more clear I've decided to create a module called traits, where I put the implementation (use traits::vec::ConvertingStack).

@ltratt @vext01 thoughts?

@ltratt
Copy link
Contributor

ltratt commented Apr 5, 2017

I wasn't totally certain if all the From conversions and so on were a help or a hindrance, to be honest...

@ltratt
Copy link
Contributor

ltratt commented Apr 6, 2017

OK let's squash some of the stuff away, and then I can merge.

@jaheba jaheba force-pushed the deopt branch 2 times, most recently from d7548f3 to 89b1e17 Compare April 6, 2017 13:30
@jaheba
Copy link
Contributor Author

jaheba commented Apr 6, 2017

I've squashed some commits together.

@ltratt
Copy link
Contributor

ltratt commented Apr 6, 2017

Can we give the "cleanup" commit a slightly more meaningful title?

@ltratt ltratt self-assigned this Apr 6, 2017
@ltratt ltratt dismissed vext01’s stale review April 6, 2017 13:40

Edd unavailable due to travelling.

@ltratt ltratt merged commit b27ac9d into master Apr 6, 2017
@ltratt ltratt deleted the deopt branch April 6, 2017 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants