-
Notifications
You must be signed in to change notification settings - Fork 0
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
Conversation
Once Jasper's tidies this up a bit, it should be ready for review by @vext01 and me. |
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? |
@vext01 Let me add a README. In short it's a tracer. |
Jasper, are you saying this is now ready for review or ...? |
Well, yes. |
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. |
Will do, let me see if rustfmt has some option for that. What style guide are you using for lrtable? |
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. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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".
src/conversions.rs
Outdated
} | ||
|
||
|
||
// impl<'a> From<&'a Instruction> for TraceInstruction { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented code.
src/conversions.rs
Outdated
use TraceInstruction as TI; | ||
|
||
match instr { | ||
I::Add => TI::Add, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commented code
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expand on this XXX?
src/tracerunner.rs
Outdated
|
||
pub struct Runner<'a, 'b: 'a> { | ||
pub trace: &'a [TraceInstruction], | ||
pub stack: Vec<Value>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
src/tracerunner.rs
Outdated
} | ||
} | ||
|
||
// XXX: return None guard succeeds |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>
.
src/tracerunner.rs
Outdated
/// 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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ;)
Added more comments. Can you point out where calls are being inlined. I can't see where this happens. |
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. |
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. |
@jaheba So I suggest you address Edd's comments (however long that takes), and then I'll have a go. Sound OK? |
I'm on it! |
@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? |
Oops, I think you accidentally committed your |
Not that accidently.
rust-lang/cargo#315
http://doc.crates.io/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries
Just gollowing the rules.
…On Wed, 5 Apr 2017 at 10:02, Laurence Tratt ***@***.***> wrote:
Oops, I think you accidentally committed your Cargo.lock file?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAIKZNs3K-i-Hp-AoLXLz4OxK071BDR5ks5rs1iQgaJpZM4Mm-D3>
.
|
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/tracerunner.rs
Outdated
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"has also" -> "have also"
src/tracerunner.rs
Outdated
let frames = guard.frame.walk().collect::<Vec<_>>(); | ||
|
||
// why .rev()? We rebuild the stack of frames, so we have to start with | ||
// the bottom most. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blank line.
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 |
Nothing I feel strongly about. Should I just remove the Cargo.lock again? |
Up to you. I don't feel strongly about it, I guess, provided that it doesn't get updated every 5 minutes. |
Alright, then I'll let it in and we see how it goes. |
I think, some clones are just unnecessary and others could be avoided. Also, since I've opted for using |
Ah, so some of the |
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 |
I wasn't totally certain if all the |
OK let's squash some of the stuff away, and then I can merge. |
d7548f3
to
89b1e17
Compare
I've squashed some commits together. |
Can we give the "cleanup" commit a slightly more meaningful title? |
Not cleaned up, but seems to work :)