From 2aea009f772a295b9ac2b87827711c7c464565b1 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 4 Dec 2016 21:31:58 +0100 Subject: [PATCH] feat: Allow the hook to yield and later resume execution By allowing the hook to yield it is possible to release the stack and allow debug info to be queried outside of the hook --- tests/debug.rs | 31 ++++++++++++++++++++++--------- vm/src/compiler.rs | 11 ++++++++--- vm/src/source_map.rs | 16 ++++++++++++---- vm/src/stack.rs | 8 +++++--- vm/src/thread.rs | 37 ++++++++++++++++++++----------------- 5 files changed, 67 insertions(+), 36 deletions(-) diff --git a/tests/debug.rs b/tests/debug.rs index d810d74c90..3c3f1ab615 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -7,7 +7,8 @@ use std::sync::{Arc, Mutex}; use std::collections::BTreeMap; use gluon::base::pos::Line; -use gluon::{Compiler, new_vm}; +use gluon::{Compiler, Error, new_vm}; +use gluon::vm; use gluon::vm::thread::{ThreadInternal, CALL_FLAG, LINE_FLAG}; @@ -50,19 +51,31 @@ fn line_hook() { let _ = env_logger::init(); let thread = new_vm(); - let lines = Arc::new(Mutex::new(Vec::new())); { - let lines = lines.clone(); let mut context = thread.context(); - context.set_hook(Some(Box::new(move |_, debug_context| { - lines.lock().unwrap().push(debug_context.stack_info(0).unwrap().line().unwrap()); - Ok(()) - }))); + context.set_hook(Some(Box::new(move |_, _| Err(vm::Error::Yield)))); context.set_hook_mask(LINE_FLAG); } - Compiler::new().implicit_prelude(false).run_expr::(&thread, "test", SIMPLE_EXPR).unwrap(); + let mut result = Compiler::new() + .implicit_prelude(false) + .run_expr::(&thread, "test", SIMPLE_EXPR) + .map(|_| ()); + + let mut lines = Vec::new(); + loop { + match result { + Ok(_) => break, + Err(Error::VM(vm::Error::Yield)) => { + let context = thread.context(); + let debug_info = context.debug_info(); + lines.push(debug_info.stack_info(0).unwrap().line().unwrap()); + } + Err(err) => panic!("{}", err), + } + result = thread.resume().map_err(From::from); + } - assert_eq!(*lines.lock().unwrap(), + assert_eq!(lines, vec![1, 3, 4, 3, 1].into_iter().map(Line::from).collect::>()); } diff --git a/vm/src/compiler.rs b/vm/src/compiler.rs index dd3c058612..e770f656eb 100644 --- a/vm/src/compiler.rs +++ b/vm/src/compiler.rs @@ -112,9 +112,11 @@ impl FunctionEnvs { self.envs.push(FunctionEnv::new(args, id, typ, compiler.source_name.clone())); } - fn end_function(&mut self, compiler: &mut Compiler) -> FunctionEnv { + fn end_function(&mut self, compiler: &mut Compiler, current_line: Line) -> FunctionEnv { compiler.stack_types.exit_scope(); compiler.stack_constructors.exit_scope(); + let instructions = self.function.instructions.len(); + self.function.source_map.close(instructions, current_line); self.envs.pop().expect("FunctionEnv in scope") } } @@ -419,9 +421,11 @@ impl<'a> Compiler<'a> { let id = self.empty_symbol.clone(); let typ = Type::function(vec![], ArcType::from(expr.env_type_of(&self.globals).clone())); + env.start_function(self, 0, id, typ); self.compile(expr, &mut env, true)?; - let FunctionEnv { function, .. } = env.end_function(self); + let current_line = self.source.line_number_at_byte(expr.span.end); + let FunctionEnv { function, .. } = env.end_function(self, current_line); Ok(function) } @@ -862,7 +866,8 @@ impl<'a> Compiler<'a> { // Insert all free variables into the above globals free variables // if they arent in that lambdas scope - let f = function.end_function(self); + let current_line = self.source.line_number_at_byte(body.span.end); + let f = function.end_function(self, current_line); for var in f.free_vars.iter() { match self.find(var, function).expect("free_vars: find") { Stack(index) => function.emit(Push(index)), diff --git a/vm/src/source_map.rs b/vm/src/source_map.rs index 6a20793100..97f3934505 100644 --- a/vm/src/source_map.rs +++ b/vm/src/source_map.rs @@ -23,18 +23,26 @@ impl SourceMap { } } + pub fn close(&mut self, instruction_index: usize, current_line: Line) { + // Push one final item to indicate the end of the function + self.map.push((instruction_index, current_line)); + } + /// Returns the line where the instruction at `instruction_index` were defined - pub fn line(&self, instruction_index: usize) -> Line { + pub fn line(&self, instruction_index: usize) -> Option { // The line for `instruction_index` is at the last index still larger than // the index in `map` let p = self.map .iter() .position(|&(index, _)| index > instruction_index) .unwrap_or(self.map.len()); - if p == 0 { - Line::from(0) + if p == 0 || + (p == self.map.len() && + instruction_index >= self.map.last().expect("Empty source_map").0) { + // instruction_index is not valid in the function + None } else { - self.map[p - 1].1 + Some(self.map[p - 1].1) } } } diff --git a/vm/src/stack.rs b/vm/src/stack.rs index d1669fc3ea..e55ff21ca7 100644 --- a/vm/src/stack.rs +++ b/vm/src/stack.rs @@ -110,9 +110,11 @@ impl Stack { match frame.state { State::Closure(ref closure) => { let line = closure.function.source_map.line(frame.instruction_index); - Some(Some(StacktraceFrame { - name: closure.function.name.clone(), - line: line, + Some(line.map(|line| { + StacktraceFrame { + name: closure.function.name.clone(), + line: line, + } })) } State::Extern(ref ext) => { diff --git a/vm/src/thread.rs b/vm/src/thread.rs index bf889eec9a..d64e122f8a 100644 --- a/vm/src/thread.rs +++ b/vm/src/thread.rs @@ -684,7 +684,6 @@ pub type HookFn = Box Result<()> + Send + Sync>; pub struct DebugInfo<'a> { stack: &'a Stack, - instruction_index: usize, state: HookFlags, } @@ -724,7 +723,7 @@ impl<'a> StackInfo<'a> { let frame = self.frame(); match frame.state { State::Closure(ref closure) => { - Some(closure.function.source_map.line(frame.instruction_index)) + closure.function.source_map.line(frame.instruction_index) } _ => None, } @@ -749,9 +748,10 @@ impl<'a> StackInfo<'a> { /// Returns an iterator over all locals available at the current executing instruction pub fn locals(&self) -> LocalIter { - match self.frame().state { + let frame = self.frame(); + match frame.state { State::Closure(ref closure) => { - closure.function.local_map.locals(self.info.instruction_index) + closure.function.local_map.locals(frame.instruction_index) } _ => LocalIter::empty(), } @@ -842,6 +842,13 @@ impl<'b> OwnedContext<'b> { let Context { ref mut gc, ref stack, .. } = **self; alloc(gc, self.thread, &stack, data) } + + pub fn debug_info(&self) -> DebugInfo { + DebugInfo { + stack: &self.stack, + state: HookFlags::empty(), + } + } } pub fn alloc(gc: &mut Gc, thread: &Thread, stack: &Stack, def: D) -> Result> @@ -889,14 +896,6 @@ impl<'b> OwnedContext<'b> { let state = context.borrow_mut().stack.frame.state; let instruction_index = context.borrow_mut().stack.frame.instruction_index; - if context.hook.flags.bits() != 0 { - if instruction_index == 0 { - context.hook.previous_instruction_index = 0; - } else { - // Reentering the function after returning from a call - context.hook.previous_instruction_index = instruction_index - 1; - } - } if instruction_index == 0 && context.hook.flags.contains(CALL_FLAG) { match state { State::Extern(_) | @@ -906,7 +905,6 @@ impl<'b> OwnedContext<'b> { if let Some(ref mut hook) = context.hook.function { let info = DebugInfo { stack: &context.stack, - instruction_index: instruction_index, state: CALL_FLAG, }; hook(thread, info)? @@ -1051,11 +1049,17 @@ impl<'b> ExecuteContext<'b> { fn enter_scope(&mut self, args: VmIndex, state: State) { self.stack.enter_scope(args, state); + self.hook.previous_instruction_index = usize::max_value(); } fn exit_scope(&mut self) -> StdResult<(), ()> { match self.stack.exit_scope() { - Ok(_) => Ok(()), + Ok(_) => { + if self.hook.flags.bits() != 0 { + self.hook.previous_instruction_index = self.stack.frame.instruction_index; + } + Ok(()) + } Err(_) => Err(()), } } @@ -1170,17 +1174,16 @@ impl<'b> ExecuteContext<'b> { let current_line = function.source_map.line(index); let previous_line = function.source_map .line(self.hook.previous_instruction_index); - if index == 0 || current_line != previous_line { + self.hook.previous_instruction_index = index; + if current_line != previous_line { self.stack.frame.instruction_index = index; self.stack.store_frame(); let info = DebugInfo { stack: &self.stack.stack, - instruction_index: index, state: LINE_FLAG, }; hook(self.thread, info)? } - self.hook.previous_instruction_index = index; } }