2022-08-27 19:49:08 +03:00
|
|
|
//! This module implements the scope-tracking logic of the Tvix
|
|
|
|
//! compiler.
|
|
|
|
//!
|
|
|
|
//! Scoping in Nix is fairly complicated, there are features like
|
|
|
|
//! mutually recursive bindings, `with`, upvalue capturing, builtin
|
|
|
|
//! poisoning and so on that introduce a fair bit of complexity.
|
|
|
|
//!
|
|
|
|
//! Tvix attempts to do as much of the heavy lifting of this at
|
|
|
|
//! compile time, and leave the runtime to mostly deal with known
|
|
|
|
//! stack indices. To do this, the compiler simulates where locals
|
|
|
|
//! will be at runtime using the data structures implemented here.
|
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
use std::{
|
|
|
|
collections::{hash_map, HashMap},
|
|
|
|
ops::Index,
|
|
|
|
};
|
2022-08-27 19:49:08 +03:00
|
|
|
|
2022-10-22 23:55:21 +03:00
|
|
|
use smol_str::SmolStr;
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
use crate::opcode::{StackIdx, UpvalueIdx};
|
|
|
|
|
2022-09-03 00:26:32 +03:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum LocalName {
|
|
|
|
/// Normally declared local with a statically known name.
|
|
|
|
Ident(String),
|
|
|
|
|
|
|
|
/// Phantom stack value (e.g. attribute set used for `with`) that
|
|
|
|
/// must be accounted for to calculate correct stack offsets.
|
|
|
|
Phantom,
|
|
|
|
}
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
/// Represents a single local already known to the compiler.
|
2022-08-29 22:27:41 +03:00
|
|
|
#[derive(Debug)]
|
2022-08-27 19:49:08 +03:00
|
|
|
pub struct Local {
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Identifier of this local. This is always a statically known
|
|
|
|
/// value (Nix does not allow dynamic identifier names in locals),
|
|
|
|
/// or a "phantom" value not accessible by users.
|
2022-09-03 00:26:32 +03:00
|
|
|
name: LocalName,
|
2022-08-27 19:49:08 +03:00
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Source span at which this local was declared.
|
2022-09-01 19:57:55 +03:00
|
|
|
pub span: codemap::Span,
|
2022-08-27 19:49:08 +03:00
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Scope depth of this local.
|
2022-08-28 15:34:35 +03:00
|
|
|
pub depth: usize,
|
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Is this local initialised?
|
2022-08-28 15:34:35 +03:00
|
|
|
pub initialised: bool,
|
2022-08-27 19:49:08 +03:00
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Is this local known to have been used at all?
|
2022-08-27 19:49:08 +03:00
|
|
|
pub used: bool,
|
2022-08-28 17:50:16 +03:00
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Does this local need to be finalised after the enclosing scope
|
|
|
|
/// is completely constructed?
|
2022-08-28 17:50:16 +03:00
|
|
|
pub needs_finaliser: bool,
|
2022-10-15 16:10:10 -07:00
|
|
|
|
|
|
|
/// Does this local's upvalues contain a reference to itself?
|
|
|
|
pub must_thunk: bool,
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|
|
|
|
|
2022-08-28 15:34:35 +03:00
|
|
|
impl Local {
|
|
|
|
/// Does this local live above the other given depth?
|
|
|
|
pub fn above(&self, theirs: usize) -> bool {
|
|
|
|
self.depth > theirs
|
|
|
|
}
|
2022-09-03 00:26:32 +03:00
|
|
|
|
|
|
|
/// Does the name of this local match the given string?
|
|
|
|
pub fn has_name(&self, other: &str) -> bool {
|
|
|
|
match &self.name {
|
|
|
|
LocalName::Ident(name) => name == other,
|
|
|
|
|
|
|
|
// Phantoms are *never* accessible by a name.
|
|
|
|
LocalName::Phantom => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-22 23:55:21 +03:00
|
|
|
/// Retrieve the name of the given local (if available).
|
|
|
|
pub fn name(&self) -> Option<SmolStr> {
|
|
|
|
match &self.name {
|
|
|
|
LocalName::Phantom => None,
|
|
|
|
LocalName::Ident(name) => Some(SmolStr::new(name)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-03 00:26:32 +03:00
|
|
|
/// Is this local intentionally ignored? (i.e. name starts with `_`)
|
|
|
|
pub fn is_ignored(&self) -> bool {
|
|
|
|
match &self.name {
|
|
|
|
LocalName::Ident(name) => name.starts_with('_'),
|
|
|
|
LocalName::Phantom => false,
|
|
|
|
}
|
|
|
|
}
|
2022-08-28 15:34:35 +03:00
|
|
|
}
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
/// Represents the current position of a local as resolved in a scope.
|
|
|
|
pub enum LocalPosition {
|
|
|
|
/// Local is not known in this scope.
|
|
|
|
Unknown,
|
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
/// Local is known at the given local index.
|
|
|
|
Known(LocalIdx),
|
2022-08-27 19:49:08 +03:00
|
|
|
|
|
|
|
/// Local is known, but is being accessed recursively within its
|
|
|
|
/// own initialisation. Depending on context, this is either an
|
|
|
|
/// error or forcing a closure/thunk.
|
2022-08-28 19:38:17 +03:00
|
|
|
Recursive(LocalIdx),
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents the different ways in which upvalues can be captured in
|
|
|
|
/// closures or thunks.
|
2022-08-31 04:56:02 +03:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
2022-09-01 18:02:06 +03:00
|
|
|
pub enum UpvalueKind {
|
2022-08-27 19:49:08 +03:00
|
|
|
/// This upvalue captures a local from the stack.
|
2022-08-28 19:38:17 +03:00
|
|
|
Local(LocalIdx),
|
2022-08-27 19:49:08 +03:00
|
|
|
|
|
|
|
/// This upvalue captures an enclosing upvalue.
|
|
|
|
Upvalue(UpvalueIdx),
|
|
|
|
}
|
|
|
|
|
2022-09-01 18:02:06 +03:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Upvalue {
|
|
|
|
pub kind: UpvalueKind,
|
2022-09-23 00:31:40 +03:00
|
|
|
pub span: codemap::Span,
|
2022-09-01 18:02:06 +03:00
|
|
|
}
|
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
/// Represents the index of a local in the scope's local array, which
|
|
|
|
/// is subtly different from its `StackIdx` (which excludes
|
|
|
|
/// uninitialised values in between).
|
|
|
|
#[repr(transparent)]
|
2022-08-31 04:56:02 +03:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
|
2022-08-28 19:38:17 +03:00
|
|
|
pub struct LocalIdx(usize);
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
/// Represents a scope known during compilation, which can be resolved
|
|
|
|
/// directly to stack indices.
|
2022-08-29 22:27:41 +03:00
|
|
|
#[derive(Debug, Default)]
|
2022-08-27 19:49:08 +03:00
|
|
|
pub struct Scope {
|
|
|
|
pub locals: Vec<Local>,
|
|
|
|
pub upvalues: Vec<Upvalue>,
|
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// How many scopes "deep" are these locals?
|
2022-09-13 16:04:52 +03:00
|
|
|
scope_depth: usize,
|
2022-08-27 19:49:08 +03:00
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Current size of the `with`-stack at runtime.
|
2022-08-27 19:55:02 +03:00
|
|
|
with_stack_size: usize,
|
2022-08-27 19:49:08 +03:00
|
|
|
|
2022-09-05 01:30:58 +03:00
|
|
|
/// Users are allowed to override globally defined symbols like
|
|
|
|
/// `true`, `false` or `null` in scopes. We call this "scope
|
|
|
|
/// poisoning", as it requires runtime resolution of those tokens.
|
|
|
|
///
|
|
|
|
/// To support this efficiently, the depth at which a poisoning
|
|
|
|
/// occured is tracked here.
|
2022-08-27 19:49:08 +03:00
|
|
|
poisoned_tokens: HashMap<&'static str, usize>,
|
|
|
|
}
|
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
impl Index<LocalIdx> for Scope {
|
|
|
|
type Output = Local;
|
|
|
|
|
|
|
|
fn index(&self, index: LocalIdx) -> &Self::Output {
|
|
|
|
&self.locals[index.0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
impl Scope {
|
|
|
|
/// Mark a globally defined token as poisoned.
|
|
|
|
pub fn poison(&mut self, name: &'static str, depth: usize) {
|
|
|
|
match self.poisoned_tokens.entry(name) {
|
|
|
|
hash_map::Entry::Occupied(_) => {
|
|
|
|
/* do nothing, as the token is already poisoned at a
|
|
|
|
* lower scope depth */
|
|
|
|
}
|
|
|
|
hash_map::Entry::Vacant(entry) => {
|
|
|
|
entry.insert(depth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-02 01:19:53 +03:00
|
|
|
/// Inherit scope details from a parent scope (required for
|
|
|
|
/// correctly nesting scopes in lambdas and thunks when special
|
|
|
|
/// scope features like poisoning are present).
|
|
|
|
pub fn inherit(&self) -> Self {
|
2022-09-03 04:20:00 +03:00
|
|
|
Self {
|
|
|
|
poisoned_tokens: self.poisoned_tokens.clone(),
|
|
|
|
..Default::default()
|
|
|
|
}
|
2022-09-02 01:19:53 +03:00
|
|
|
}
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
/// Check whether a given token is poisoned.
|
|
|
|
pub fn is_poisoned(&self, name: &str) -> bool {
|
|
|
|
self.poisoned_tokens.contains_key(name)
|
|
|
|
}
|
|
|
|
|
2022-09-06 17:05:08 +03:00
|
|
|
/// "Unpoison" tokens that were poisoned at the current depth.
|
|
|
|
/// Used when scopes are closed.
|
|
|
|
fn unpoison(&mut self) {
|
2022-08-27 19:49:08 +03:00
|
|
|
self.poisoned_tokens
|
2022-09-06 17:05:08 +03:00
|
|
|
.retain(|_, poisoned_at| *poisoned_at != self.scope_depth);
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|
|
|
|
|
2022-08-27 19:55:02 +03:00
|
|
|
/// Increase the `with`-stack size of this scope.
|
|
|
|
pub fn push_with(&mut self) {
|
|
|
|
self.with_stack_size += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Decrease the `with`-stack size of this scope.
|
|
|
|
pub fn pop_with(&mut self) {
|
|
|
|
self.with_stack_size -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Does this scope currently require dynamic runtime resolution
|
|
|
|
/// of identifiers that could not be found?
|
|
|
|
pub fn has_with(&self) -> bool {
|
|
|
|
self.with_stack_size > 0
|
|
|
|
}
|
|
|
|
|
2022-08-27 19:49:08 +03:00
|
|
|
/// Resolve the stack index of a statically known local.
|
|
|
|
pub fn resolve_local(&mut self, name: &str) -> LocalPosition {
|
|
|
|
for (idx, local) in self.locals.iter_mut().enumerate().rev() {
|
2022-09-03 00:26:32 +03:00
|
|
|
if local.has_name(name) {
|
2022-08-27 19:49:08 +03:00
|
|
|
local.used = true;
|
|
|
|
|
2022-08-28 15:34:35 +03:00
|
|
|
// This local is still being initialised, meaning that
|
|
|
|
// we know its final runtime stack position, but it is
|
|
|
|
// not yet on the stack.
|
|
|
|
if !local.initialised {
|
2022-08-28 19:38:17 +03:00
|
|
|
return LocalPosition::Recursive(LocalIdx(idx));
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|
2022-08-28 15:34:35 +03:00
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
return LocalPosition::Known(LocalIdx(idx));
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LocalPosition::Unknown
|
|
|
|
}
|
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
/// Declare a local variable that occupies a stack slot and should
|
|
|
|
/// be accounted for, but is not directly accessible by users
|
|
|
|
/// (e.g. attribute sets used for `with`).
|
2022-09-06 17:12:04 +03:00
|
|
|
pub fn declare_phantom(&mut self, span: codemap::Span, initialised: bool) -> LocalIdx {
|
2022-08-28 19:38:17 +03:00
|
|
|
let idx = self.locals.len();
|
|
|
|
self.locals.push(Local {
|
2022-09-06 17:12:04 +03:00
|
|
|
initialised,
|
2022-09-01 19:57:55 +03:00
|
|
|
span,
|
2022-09-06 17:12:04 +03:00
|
|
|
name: LocalName::Phantom,
|
2022-08-28 19:38:17 +03:00
|
|
|
depth: self.scope_depth,
|
|
|
|
needs_finaliser: false,
|
2022-10-15 16:10:10 -07:00
|
|
|
must_thunk: false,
|
2022-08-28 19:38:17 +03:00
|
|
|
used: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
LocalIdx(idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Declare an uninitialised local variable.
|
2022-09-01 19:57:55 +03:00
|
|
|
pub fn declare_local(&mut self, name: String, span: codemap::Span) -> LocalIdx {
|
2022-08-28 19:38:17 +03:00
|
|
|
let idx = self.locals.len();
|
|
|
|
self.locals.push(Local {
|
2022-09-03 00:26:32 +03:00
|
|
|
name: LocalName::Ident(name),
|
2022-09-01 19:57:55 +03:00
|
|
|
span,
|
2022-08-28 19:38:17 +03:00
|
|
|
depth: self.scope_depth,
|
|
|
|
initialised: false,
|
|
|
|
needs_finaliser: false,
|
2022-10-15 16:10:10 -07:00
|
|
|
must_thunk: false,
|
2022-08-28 19:38:17 +03:00
|
|
|
used: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
LocalIdx(idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Mark local as initialised after compiling its expression.
|
|
|
|
pub fn mark_initialised(&mut self, idx: LocalIdx) {
|
|
|
|
self.locals[idx.0].initialised = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Mark local as needing a finaliser.
|
|
|
|
pub fn mark_needs_finaliser(&mut self, idx: LocalIdx) {
|
|
|
|
self.locals[idx.0].needs_finaliser = true;
|
|
|
|
}
|
|
|
|
|
2022-10-15 16:10:10 -07:00
|
|
|
/// Mark local as must be wrapped in a thunk. This happens if
|
|
|
|
/// the local has a reference to itself in its upvalues.
|
|
|
|
pub fn mark_must_thunk(&mut self, idx: LocalIdx) {
|
|
|
|
self.locals[idx.0].must_thunk = true;
|
|
|
|
}
|
|
|
|
|
2022-08-28 19:38:17 +03:00
|
|
|
/// Compute the runtime stack index for a given local by
|
|
|
|
/// accounting for uninitialised variables at scopes below this
|
|
|
|
/// one.
|
|
|
|
pub fn stack_index(&self, idx: LocalIdx) -> StackIdx {
|
|
|
|
let uninitialised_count = self.locals[..(idx.0)]
|
|
|
|
.iter()
|
|
|
|
.filter(|l| !l.initialised && self[idx].above(l.depth))
|
|
|
|
.count();
|
|
|
|
|
|
|
|
StackIdx(idx.0 - uninitialised_count)
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|
2022-09-06 17:05:08 +03:00
|
|
|
|
2022-09-13 16:04:52 +03:00
|
|
|
/// Increase the current scope depth (e.g. within a new bindings
|
|
|
|
/// block, or `with`-scope).
|
|
|
|
pub fn begin_scope(&mut self) {
|
|
|
|
self.scope_depth += 1;
|
|
|
|
}
|
|
|
|
|
2022-09-06 17:05:08 +03:00
|
|
|
/// Decrease the scope depth and remove all locals still tracked
|
|
|
|
/// for the current scope.
|
|
|
|
///
|
|
|
|
/// Returns the count of locals that were dropped while marked as
|
|
|
|
/// initialised (used by the compiler to determine whether to emit
|
|
|
|
/// scope cleanup operations), as well as the spans of the
|
|
|
|
/// definitions of unused locals (used by the compiler to emit
|
|
|
|
/// unused binding warnings).
|
|
|
|
pub fn end_scope(&mut self) -> (usize, Vec<codemap::Span>) {
|
|
|
|
debug_assert!(self.scope_depth != 0, "can not end top scope");
|
|
|
|
|
|
|
|
// If this scope poisoned any builtins or special identifiers,
|
|
|
|
// they need to be reset.
|
|
|
|
self.unpoison();
|
|
|
|
|
|
|
|
let mut pops = 0;
|
|
|
|
let mut unused_spans = vec![];
|
|
|
|
|
|
|
|
// TL;DR - iterate from the back while things belonging to the
|
|
|
|
// ended scope still exist.
|
|
|
|
while self.locals.last().unwrap().depth == self.scope_depth {
|
|
|
|
if let Some(local) = self.locals.pop() {
|
|
|
|
// pop the local from the stack if it was actually
|
|
|
|
// initialised
|
|
|
|
if local.initialised {
|
|
|
|
pops += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// analyse whether the local was accessed during its
|
|
|
|
// lifetime, and emit a warning otherwise (unless the
|
|
|
|
// user explicitly chose to ignore it by prefixing the
|
|
|
|
// identifier with `_`)
|
|
|
|
if !local.used && !local.is_ignored() {
|
|
|
|
unused_spans.push(local.span);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-11 15:00:06 +03:00
|
|
|
self.scope_depth -= 1;
|
|
|
|
|
2022-09-06 17:05:08 +03:00
|
|
|
(pops, unused_spans)
|
|
|
|
}
|
2022-09-13 16:04:52 +03:00
|
|
|
|
|
|
|
/// Access the current scope depth.
|
|
|
|
pub fn scope_depth(&self) -> usize {
|
|
|
|
self.scope_depth
|
|
|
|
}
|
2022-08-27 19:49:08 +03:00
|
|
|
}
|