feat(tvix/eval): implement OpTailCall
If the last operation within a chunk is a function call, the call can be executed in the same call frame without increasing the depth of the call stack. To enable this, a new OpTailCall instruction (similar to OpCall) is introduced, but not yet emitted by the compiler. Change-Id: I9ffbd7da6d2d6a8ec7a724646435dc6ee89712f2 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6457 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
		
							parent
							
								
									6deaa0d6ce
								
							
						
					
					
						commit
						2e018a50a7
					
				
					 3 changed files with 52 additions and 11 deletions
				
			
		| 
						 | 
					@ -41,6 +41,10 @@ pub trait Observer {
 | 
				
			||||||
    /// Called when the runtime exits a call frame.
 | 
					    /// Called when the runtime exits a call frame.
 | 
				
			||||||
    fn observe_exit_frame(&mut self, _frame_at: usize) {}
 | 
					    fn observe_exit_frame(&mut self, _frame_at: usize) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Called when the runtime replaces the current call frame for a
 | 
				
			||||||
 | 
					    /// tail call.
 | 
				
			||||||
 | 
					    fn observe_tail_call(&mut self, _frame_at: usize, _: &Rc<Lambda>) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Called when the runtime enters a builtin.
 | 
					    /// Called when the runtime enters a builtin.
 | 
				
			||||||
    fn observe_enter_builtin(&mut self, _name: &'static str) {}
 | 
					    fn observe_enter_builtin(&mut self, _name: &'static str) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -150,6 +154,14 @@ impl<W: Write> Observer for TracingObserver<W> {
 | 
				
			||||||
        let _ = writeln!(&mut self.writer, "=== exiting builtin {} ===", name);
 | 
					        let _ = writeln!(&mut self.writer, "=== exiting builtin {} ===", name);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
 | 
				
			||||||
 | 
					        let _ = writeln!(
 | 
				
			||||||
 | 
					            &mut self.writer,
 | 
				
			||||||
 | 
					            "=== tail-calling {:p} in frame[{}] ===",
 | 
				
			||||||
 | 
					            lambda, frame_at
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn observe_execute_op(&mut self, ip: usize, op: &OpCode, stack: &[Value]) {
 | 
					    fn observe_execute_op(&mut self, ip: usize, op: &OpCode, stack: &[Value]) {
 | 
				
			||||||
        let _ = write!(&mut self.writer, "{:04} {:?}\t[ ", ip, op);
 | 
					        let _ = write!(&mut self.writer, "{:04} {:?}\t[ ", ip, op);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,6 +104,7 @@ pub enum OpCode {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Lambdas & closures
 | 
					    // Lambdas & closures
 | 
				
			||||||
    OpCall,
 | 
					    OpCall,
 | 
				
			||||||
 | 
					    OpTailCall,
 | 
				
			||||||
    OpGetUpvalue(UpvalueIdx),
 | 
					    OpGetUpvalue(UpvalueIdx),
 | 
				
			||||||
    OpClosure(ConstantIdx),
 | 
					    OpClosure(ConstantIdx),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ use crate::{
 | 
				
			||||||
    observer::Observer,
 | 
					    observer::Observer,
 | 
				
			||||||
    opcode::{CodeIdx, ConstantIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
 | 
					    opcode::{CodeIdx, ConstantIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
 | 
				
			||||||
    upvalues::UpvalueCarrier,
 | 
					    upvalues::UpvalueCarrier,
 | 
				
			||||||
    value::{Closure, Lambda, NixAttrs, NixList, Thunk, Value},
 | 
					    value::{Builtin, Closure, Lambda, NixAttrs, NixList, Thunk, Value},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct CallFrame {
 | 
					struct CallFrame {
 | 
				
			||||||
| 
						 | 
					@ -447,21 +447,35 @@ impl<'o> VM<'o> {
 | 
				
			||||||
                            self.push(result)
 | 
					                            self.push(result)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        Value::Builtin(builtin) => {
 | 
					                        Value::Builtin(builtin) => self.call_builtin(builtin)?,
 | 
				
			||||||
                            let builtin_name = builtin.name();
 | 
					 | 
				
			||||||
                            self.observer.observe_enter_builtin(builtin_name);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let arg = self.pop();
 | 
					 | 
				
			||||||
                            let result = fallible!(self, builtin.apply(self, arg));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            self.observer.observe_exit_builtin(builtin_name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            self.push(result);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        _ => return Err(self.error(ErrorKind::NotCallable)),
 | 
					                        _ => return Err(self.error(ErrorKind::NotCallable)),
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                OpCode::OpTailCall => {
 | 
				
			||||||
 | 
					                    let callable = self.pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    match callable {
 | 
				
			||||||
 | 
					                        Value::Builtin(builtin) => self.call_builtin(builtin)?,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Value::Closure(closure) => {
 | 
				
			||||||
 | 
					                            let lambda = closure.lambda();
 | 
				
			||||||
 | 
					                            self.observer.observe_tail_call(self.frames.len(), &lambda);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // Replace the current call frames
 | 
				
			||||||
 | 
					                            // internals with that of the tail-called
 | 
				
			||||||
 | 
					                            // closure.
 | 
				
			||||||
 | 
					                            let mut frame = self.frame_mut();
 | 
				
			||||||
 | 
					                            frame.lambda = lambda;
 | 
				
			||||||
 | 
					                            frame.upvalues = closure.upvalues().to_vec();
 | 
				
			||||||
 | 
					                            frame.ip = 0; // reset instruction pointer to beginning
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        _ => return Err(self.error(ErrorKind::NotCallable)),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                OpCode::OpGetUpvalue(upv_idx) => {
 | 
					                OpCode::OpGetUpvalue(upv_idx) => {
 | 
				
			||||||
                    let value = self.frame().upvalue(upv_idx).clone();
 | 
					                    let value = self.frame().upvalue(upv_idx).clone();
 | 
				
			||||||
                    if let Value::DynamicUpvalueMissing(name) = value {
 | 
					                    if let Value::DynamicUpvalueMissing(name) = value {
 | 
				
			||||||
| 
						 | 
					@ -699,6 +713,20 @@ impl<'o> VM<'o> {
 | 
				
			||||||
            _ => Ok(()),
 | 
					            _ => Ok(()),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn call_builtin(&mut self, builtin: Builtin) -> EvalResult<()> {
 | 
				
			||||||
 | 
					        let builtin_name = builtin.name();
 | 
				
			||||||
 | 
					        self.observer.observe_enter_builtin(builtin_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let arg = self.pop();
 | 
				
			||||||
 | 
					        let result = fallible!(self, builtin.apply(self, arg));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.observer.observe_exit_builtin(builtin_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.push(result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: use Rc::unwrap_or_clone once it is stabilised.
 | 
					// TODO: use Rc::unwrap_or_clone once it is stabilised.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue