feat(tvix/eval): implement compilation of upvalue access
This adds a new upvalue tracking structure in the compiler to resolve upvalues and track their positions within a function when compiling a closure. The compiler will emit runtime upvalue access instructions after this commit, but the creation of the runtime closure object etc. is not yet wired up. Change-Id: Ib0c2c25f686bfd45f797c528753068858e3a770d Reviewed-on: https://cl.tvl.fyi/c/depot/+/6289 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi>
This commit is contained in:
		
							parent
							
								
									2f93ed297e
								
							
						
					
					
						commit
						1163ef3e41
					
				
					 4 changed files with 66 additions and 4 deletions
				
			
		|  | @ -22,7 +22,7 @@ use std::rc::Rc; | |||
| 
 | ||||
| use crate::chunk::Chunk; | ||||
| use crate::errors::{Error, ErrorKind, EvalResult}; | ||||
| use crate::opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx}; | ||||
| use crate::opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx}; | ||||
| use crate::value::{Closure, Lambda, Value}; | ||||
| use crate::warnings::{EvalWarning, WarningKind}; | ||||
| 
 | ||||
|  | @ -63,6 +63,15 @@ struct With { | |||
|     depth: usize, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq)] | ||||
| enum Upvalue { | ||||
|     /// This upvalue captures a local from the stack.
 | ||||
|     Stack(StackIdx), | ||||
| 
 | ||||
|     /// This upvalue captures an enclosing upvalue.
 | ||||
|     Upvalue(UpvalueIdx), | ||||
| } | ||||
| 
 | ||||
| /// Represents a scope known during compilation, which can be resolved
 | ||||
| /// directly to stack indices.
 | ||||
| ///
 | ||||
|  | @ -72,6 +81,7 @@ struct With { | |||
| #[derive(Default)] | ||||
| struct Scope { | ||||
|     locals: Vec<Local>, | ||||
|     upvalues: Vec<Upvalue>, | ||||
| 
 | ||||
|     // How many scopes "deep" are these locals?
 | ||||
|     scope_depth: usize, | ||||
|  | @ -772,13 +782,19 @@ impl Compiler { | |||
|         match self.scope_mut().resolve_local(ident.text()) { | ||||
|             Some(idx) => self.chunk().push_op(OpCode::OpGetLocal(idx)), | ||||
|             None => { | ||||
|                 // Are we possibly dealing with an upvalue?
 | ||||
|                 if let Some(idx) = self.resolve_upvalue(self.contexts.len() - 1, ident.text()) { | ||||
|                     self.chunk().push_op(OpCode::OpGetUpvalue(idx)); | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 if self.scope().with_stack.is_empty() { | ||||
|                     self.emit_error(node.syntax().clone(), ErrorKind::UnknownStaticVariable); | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 // Variable needs to be dynamically resolved
 | ||||
|                 // at runtime.
 | ||||
|                 // Variable needs to be dynamically resolved at
 | ||||
|                 // runtime.
 | ||||
|                 self.emit_constant(Value::String(ident.text().into())); | ||||
|                 self.chunk().push_op(OpCode::OpResolveWith) | ||||
|             } | ||||
|  | @ -976,6 +992,42 @@ impl Compiler { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     fn resolve_upvalue(&mut self, ctx_idx: usize, name: &str) -> Option<UpvalueIdx> { | ||||
|         if ctx_idx == 0 { | ||||
|             // There can not be any upvalue at the outermost context.
 | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         if let Some(idx) = self.contexts[ctx_idx - 1].scope.resolve_local(name) { | ||||
|             return Some(self.add_upvalue(ctx_idx, Upvalue::Stack(idx))); | ||||
|         } | ||||
| 
 | ||||
|         // If the upvalue comes from an enclosing context, we need to
 | ||||
|         // recurse to make sure that the upvalues are created at each
 | ||||
|         // level.
 | ||||
|         if let Some(idx) = self.resolve_upvalue(ctx_idx - 1, name) { | ||||
|             return Some(self.add_upvalue(ctx_idx, Upvalue::Upvalue(idx))); | ||||
|         } | ||||
| 
 | ||||
|         None | ||||
|     } | ||||
| 
 | ||||
|     fn add_upvalue(&mut self, ctx_idx: usize, upvalue: Upvalue) -> UpvalueIdx { | ||||
|         // If there is already an upvalue closing over the specified
 | ||||
|         // index, retrieve that instead.
 | ||||
|         for (idx, existing) in self.contexts[ctx_idx].scope.upvalues.iter().enumerate() { | ||||
|             if *existing == upvalue { | ||||
|                 return UpvalueIdx(idx); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         self.contexts[ctx_idx].scope.upvalues.push(upvalue); | ||||
| 
 | ||||
|         let idx = UpvalueIdx(self.contexts[ctx_idx].lambda.upvalue_count); | ||||
|         self.contexts[ctx_idx].lambda.upvalue_count += 1; | ||||
|         idx | ||||
|     } | ||||
| 
 | ||||
|     fn emit_warning(&mut self, node: rnix::SyntaxNode, kind: WarningKind) { | ||||
|         self.warnings.push(EvalWarning { node, kind }) | ||||
|     } | ||||
|  |  | |||
|  | @ -13,9 +13,14 @@ pub struct CodeIdx(pub usize); | |||
| 
 | ||||
| /// Index of a value in the runtime stack.
 | ||||
| #[repr(transparent)] | ||||
| #[derive(Clone, Copy, Debug)] | ||||
| #[derive(Clone, Copy, Debug, PartialEq)] | ||||
| pub struct StackIdx(pub usize); | ||||
| 
 | ||||
| /// Index of an upvalue within a closure's upvalue list.
 | ||||
| #[repr(transparent)] | ||||
| #[derive(Clone, Copy, Debug, PartialEq)] | ||||
| pub struct UpvalueIdx(pub usize); | ||||
| 
 | ||||
| /// Offset by which an instruction pointer should change in a jump.
 | ||||
| #[repr(transparent)] | ||||
| #[derive(Clone, Copy, Debug)] | ||||
|  | @ -99,4 +104,5 @@ pub enum OpCode { | |||
| 
 | ||||
|     // Lambdas
 | ||||
|     OpCall, | ||||
|     OpGetUpvalue(UpvalueIdx), | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ use crate::chunk::Chunk; | |||
| pub struct Lambda { | ||||
|     // name: Option<NixString>,
 | ||||
|     pub(crate) chunk: Rc<Chunk>, | ||||
|     pub(crate) upvalue_count: usize, | ||||
| } | ||||
| 
 | ||||
| impl Lambda { | ||||
|  | @ -14,6 +15,7 @@ impl Lambda { | |||
|         Lambda { | ||||
|             // name: None,
 | ||||
|             chunk: Default::default(), | ||||
|             upvalue_count: 0, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -366,6 +366,8 @@ impl VM { | |||
|                         _ => return Err(ErrorKind::NotCallable.into()), | ||||
|                     }; | ||||
|                 } | ||||
| 
 | ||||
|                 OpCode::OpGetUpvalue(_) => todo!("getting upvalues"), | ||||
|             } | ||||
| 
 | ||||
|             #[cfg(feature = "disassembler")] | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue