sphinx/codegen/
scope.rs

1// Scope Tracking
2
3use crate::language::{InternSymbol, Access};
4use crate::parser::stmt::Label;
5use crate::debug::symbol::DebugSymbol;
6use crate::codegen::JumpSite;
7use crate::codegen::opcodes::{LocalIndex, UpvalueIndex};
8use crate::codegen::funproto::UpvalueTarget;
9use crate::codegen::errors::CompileResult;
10
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub(super) enum LocalName {
14    // local variable names defined by AST string symbols
15    Symbol(InternSymbol),
16    
17    // special local variables
18    
19    // these are created by the VM
20    Receiver,  // inside a function call, this refers to the object that was called
21    NArgs,     // inside a function call, the number of arguments passed at the call site
22    
23    Anonymous, // for internal temporaries. excluded from local variable resolution, they can only be referred to by local index
24}
25
26
27#[derive(Debug, Clone)]
28pub(super) struct Local {
29    mode: Access,
30    name: LocalName,
31    index: LocalIndex,
32    captured: bool, // tracks whether the local is being referenced by an upvalue
33}
34
35impl Local {
36    pub(super) fn mode(&self) -> Access { self.mode }
37    pub(super) fn name(&self) -> LocalName { self.name }
38    pub(super) fn index(&self) -> LocalIndex { self.index }
39    pub(super) fn captured(&self) -> bool { self.captured }
40}
41
42#[derive(Clone, Copy)]
43pub(super) enum InsertLocal {
44    CreateNew(LocalIndex),
45    HideExisting(LocalIndex),
46}
47
48impl From<InsertLocal> for LocalIndex {
49    fn from(result: InsertLocal) -> Self {
50        match result {
51            InsertLocal::CreateNew(local_index) => local_index,
52            InsertLocal::HideExisting(local_index) => local_index,
53        }
54    }
55}
56
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub(super) enum ScopeTag {
60    Block,
61    Loop,
62    Branch,
63    Function,
64    Global,
65    Temporary,
66}
67
68impl ScopeTag {
69    fn accepts_control_flow(&self, control_flow: ControlFlowTarget) -> bool {
70        match self {
71            Self::Block => matches!(control_flow,
72                ControlFlowTarget::Break(..)
73            ),
74            
75            Self::Loop => matches!(control_flow,
76                ControlFlowTarget::Break(..) | ControlFlowTarget::Continue(..)
77            ),
78            
79            _ => false,
80        }
81    }
82    
83    fn hide_from_nro(&self) -> bool {
84        match self {
85            Self::Temporary => true,
86            _ => false,
87        }
88    }
89    
90    pub(super) fn is_expr_block(&self) -> bool {
91        match self {
92            Self::Block | Self:: Branch => true,
93            _ => false,
94        }
95    }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
99pub(super) enum ControlFlowTarget {
100    Break(Option<Label>),
101    Continue(Option<Label>),
102}
103
104impl ControlFlowTarget {
105    pub(super) fn label(&self) -> Option<&Label> {
106        match self {
107            Self::Break(label) => label.as_ref(),
108            Self::Continue(label) => label.as_ref(),
109        }
110    }
111}
112
113// track break/continue jump sites
114#[derive(Debug, Default)]
115struct ControlFlowTracker {
116    label: Option<Label>,
117    continue_sites: Vec<JumpSite>,
118    break_sites: Vec<JumpSite>,
119}
120
121impl ControlFlowTracker {
122    fn new(label: Option<Label>) -> Self {
123        Self {
124            label,
125            continue_sites: Vec::new(),
126            break_sites: Vec::new(),
127        }
128    }
129}
130
131#[derive(Debug)]
132pub(super) struct Scope {
133    tag: ScopeTag,
134    depth: usize,
135    symbol: Option<DebugSymbol>,
136    prev_index: Option<LocalIndex>,
137    locals: Vec<Local>,
138    control_flow: ControlFlowTracker,
139}
140
141impl Scope {
142    pub(super) fn tag(&self) -> ScopeTag {
143        self.tag
144    }
145    
146    pub(super) fn depth(&self) -> usize {
147        self.depth
148    }
149    
150    pub(super) fn locals(&self) -> &[Local] {
151        self.locals.as_slice()
152    }
153    
154    pub(super) fn debug_symbol(&self) -> Option<&DebugSymbol> {
155        self.symbol.as_ref()
156    }
157    
158    pub(super) fn register_continue(&mut self, continue_site: JumpSite) {
159        self.control_flow.continue_sites.push(continue_site)
160    }
161    
162    pub(super) fn continue_sites(&self) -> &[JumpSite] {
163        &self.control_flow.continue_sites
164    }
165    
166    pub(super) fn register_break(&mut self, break_site: JumpSite) {
167        self.control_flow.break_sites.push(break_site)
168    }
169    
170    pub(super) fn break_sites(&self) -> &[JumpSite] {
171        &self.control_flow.break_sites
172    }
173    
174    fn control_flow_mut(&mut self) -> &mut ControlFlowTracker {
175        &mut self.control_flow
176    }
177    
178    fn last_index(&self) -> Option<LocalIndex> {
179        self.locals.last().map_or(self.prev_index, |local| Some(local.index))
180    }
181    
182    fn find_local(&self, name: &LocalName) -> Option<&Local> {
183        if matches!(name, LocalName::Anonymous) {
184            return None; // anonymous temporaries should not be referenced
185        }
186        self.locals.iter().find(|local| local.name == *name)
187    }
188    
189    fn find_local_mut(&mut self, name: &LocalName) -> Option<&mut Local> {
190        if matches!(name, LocalName::Anonymous) {
191            return None; // anonymous temporaries should not be referenced
192        }
193        self.locals.iter_mut().find(|local| local.name == *name)
194    }
195    
196    fn push_local(&mut self, mode: Access, name: LocalName) -> CompileResult<&Local> {
197        let index = self.last_index().map_or(
198            Ok(0),
199            |index| index.checked_add(1)
200                .ok_or("local variable limit reached")
201        )?;
202        
203        let local = Local {
204            mode, name, index, 
205            captured: false,
206        };
207        
208        self.locals.push(local);
209        Ok(self.locals.last().unwrap())
210    }
211    
212    fn insert_local(&mut self, mode: Access, name: LocalName) -> CompileResult<InsertLocal> {
213        // ensure only anonymous variables get inserted into hidden scopes
214        if self.tag.hide_from_nro() {
215            debug_assert!(name == LocalName::Anonymous);
216        }
217        
218        // see if this local already exists in the current scope
219        if let Some(mut local) = self.find_local_mut(&name) {
220            (*local).mode = mode; // redeclare with new mutability
221            Ok(InsertLocal::HideExisting(local.index))
222        } else {
223            let local = self.push_local(mode, name)?;
224            Ok(InsertLocal::CreateNew(local.index))
225        }
226    }
227}
228
229
230#[derive(Debug)]
231struct NestedScopes {
232    toplevel: Scope,
233    nested: Vec<Scope>,
234}
235
236impl NestedScopes {
237    fn new(symbol: Option<&DebugSymbol>, tag: ScopeTag, label: Option<Label>) -> Self {
238        let toplevel = Scope {
239            tag,
240            depth: 0,
241            prev_index: None,
242            symbol: symbol.copied(),
243            locals: Vec::new(),
244            control_flow: ControlFlowTracker::new(label),
245        };
246        
247        Self {
248            toplevel,
249            nested: Vec::new(),
250        }
251    }
252    
253    fn is_nested(&self) -> bool {
254        !self.nested.is_empty()
255    }
256    
257    fn current_scope(&self) -> &Scope {
258        self.nested.last().unwrap_or(&self.toplevel)
259    }
260    
261    fn current_scope_mut(&mut self) -> &mut Scope {
262        self.nested.last_mut().unwrap_or(&mut self.toplevel)
263    }
264    
265    fn push_scope(&mut self, symbol: Option<&DebugSymbol>, label: Option<Label>, tag: ScopeTag) {
266        let current_scope = self.current_scope();
267        
268        let scope = Scope {
269            tag,
270            depth: current_scope.depth() + 1,
271            prev_index: current_scope.last_index(),
272            symbol: symbol.copied(),
273            locals: Vec::new(),
274            control_flow: ControlFlowTracker::new(label),
275        };
276        
277        self.nested.push(scope);
278    }
279    
280    fn pop_scope(&mut self) -> Scope {
281        self.nested.pop().expect("pop toplevel scope")
282    }
283    
284    /// Iterate in name resolution order
285    fn iter_nro(&self) -> impl Iterator<Item=&Scope> {
286        self.nested.iter().rev()
287            .chain(std::iter::once(&self.toplevel))
288            .filter(|scope| !scope.tag().hide_from_nro())
289    }
290    
291    fn iter_nro_mut(&mut self) -> impl Iterator<Item=&mut Scope> {
292        self.nested.iter_mut().rev()
293            .chain(std::iter::once(&mut self.toplevel))
294            .filter(|scope| !scope.tag().hide_from_nro())
295    }
296}
297
298
299#[derive(Debug, Clone)]
300pub(super) struct Upvalue {
301    mode: Access,
302    name: LocalName,
303    index: UpvalueIndex,
304    target: UpvalueTarget,
305}
306
307impl Upvalue {
308    pub(super) fn mode(&self) -> Access { self.mode }
309    pub(super) fn name(&self) -> LocalName { self.name }
310    pub(super) fn index(&self) -> UpvalueIndex { self.index }
311    pub(super) fn target(&self) -> UpvalueTarget { self.target }
312}
313
314
315#[derive(Debug)]
316pub(super) struct CallFrame {
317    scopes: NestedScopes,
318    upvalues: Vec<Upvalue>,
319}
320
321impl CallFrame {
322    fn new(symbol: Option<&DebugSymbol>) -> Self {
323        Self {
324            scopes: NestedScopes::new(symbol, ScopeTag::Function, None),
325            upvalues: Vec::new(),
326        }
327    }
328    
329    pub(super) fn upvalues(&self) -> &[Upvalue] { self.upvalues.as_slice() }
330    
331    pub(super) fn iter_locals(&self) -> impl Iterator<Item=&Local> {
332        self.scopes().iter_nro().flat_map(|scope| scope.locals().iter())
333    }
334    
335    fn scopes(&self) -> &NestedScopes { &self.scopes }
336    
337    fn scopes_mut(&mut self) -> &mut NestedScopes { &mut self.scopes }
338    
339    fn find_upval(&self, name: &LocalName) -> Option<&Upvalue> {
340        self.upvalues.iter().find(|upval| upval.name == *name)
341    }
342    
343    fn create_upval_for_local(&mut self, local: &mut Local) -> CompileResult<&Upvalue> {
344        let index = UpvalueIndex::try_from(self.upvalues.len())
345            .map_err(|_| "upvalue limit reached")?;
346        
347        let upval = Upvalue {
348            index,
349            mode: local.mode,
350            name: local.name,
351            target: UpvalueTarget::Local(local.index),
352        };
353        self.upvalues.push(upval);
354        
355        local.captured = true;
356        
357        Ok(self.upvalues.last().unwrap())
358    }
359    
360    fn create_upval_for_upval(&mut self, upval: &Upvalue) -> CompileResult<&Upvalue> {
361        let index = UpvalueIndex::try_from(self.upvalues.len())
362            .map_err(|_| "upvalue limit reached")?;
363        
364        let upval = Upvalue {
365            index,
366            mode: upval.mode,
367            name: upval.name,
368            target: UpvalueTarget::Upvalue(upval.index),
369        };
370        
371        self.upvalues.push(upval);
372        
373        Ok(self.upvalues.last().unwrap())
374    }
375}
376
377
378#[derive(Debug)]
379pub(super) struct ScopeTracker {
380    toplevel: NestedScopes,
381    frames: Vec<CallFrame>,
382}
383
384impl ScopeTracker {
385    pub(super) fn new() -> Self {
386        Self {
387            toplevel: NestedScopes::new(None, ScopeTag::Global, None),
388            frames: Vec::new(),
389        }
390    }
391    
392    pub(super) fn is_global_scope(&self) -> bool {
393        // return true if the first non-temporary is global
394        self.get_current_scope(true).tag() == ScopeTag::Global
395    }
396    
397    pub(super) fn is_temporary_scope(&self) -> bool {
398        self.local_scopes().current_scope().tag() == ScopeTag::Temporary
399    }
400    
401    pub(super) fn is_call_frame(&self) -> bool {
402        !self.frames.is_empty()
403    }
404    
405    pub(super) fn push_frame(&mut self, symbol: Option<&DebugSymbol>) {
406        self.frames.push(CallFrame::new(symbol))
407    }
408    
409    pub(super) fn pop_frame(&mut self) -> CallFrame {
410        self.frames.pop().expect("pop empty frames")
411    }
412    
413    fn local_scopes(&self) -> &NestedScopes {
414        self.frames.last()
415            .map_or(&self.toplevel, |frame| frame.scopes())
416    }
417    
418    fn local_scopes_mut(&mut self) -> &mut NestedScopes {
419        self.frames.last_mut()
420            .map_or(&mut self.toplevel, |frame| frame.scopes_mut())
421    }
422    
423    fn get_current_scope(&self, ignore_temp: bool) -> &Scope {
424        if ignore_temp {
425            self.local_scopes()
426                .iter_nro().next()
427                .expect("empty nro")
428        } else {
429            self.local_scopes().current_scope()
430        }
431    }
432    
433    fn get_current_scope_mut(&mut self, ignore_temp: bool) -> &mut Scope {
434        if ignore_temp {
435            self.local_scopes_mut()
436                .iter_nro_mut().next()
437                .expect("empty nro")
438        } else {
439            self.local_scopes_mut().current_scope_mut()
440        }
441    }
442    
443    // scopes
444    
445    pub(super) fn push_scope(&mut self, symbol: Option<&DebugSymbol>, label: Option<Label>, tag: ScopeTag) -> &mut Scope {
446        let local_scope = self.local_scopes_mut();
447        local_scope.push_scope(symbol, label, tag);
448        local_scope.current_scope_mut()
449    }
450    
451    pub(super) fn pop_scope(&mut self) -> Scope {
452        let scope = self.local_scopes_mut().pop_scope();
453        scope
454    }
455    
456    // local variables
457    
458    pub(super) fn insert_local(&mut self, mode: Access, name: LocalName) -> CompileResult<InsertLocal> {
459        self.get_current_scope_mut(name != LocalName::Anonymous)
460            .insert_local(mode, name)
461    }
462    
463    pub(super) fn resolve_local(&self, name: &LocalName) -> Option<&Local> {
464        self.local_scopes()
465            .iter_nro().find_map(|scope| scope.find_local(name))
466    }
467    
468    // upvalues
469    
470    pub(super) fn resolve_or_create_upval(&mut self, name: &LocalName) -> CompileResult<Option<&Upvalue>> {
471        if self.frames.is_empty() {
472            return Ok(None);
473        }
474        
475        let frame_idx = self.frames.len() - 1;
476        let upval = self.resolve_upval_helper(name, frame_idx)?
477            .map(|idx| &self.frames.last().unwrap().upvalues[usize::from(idx)]);
478        
479        Ok(upval)
480    }
481    
482    // recursive helper
483    fn resolve_upval_helper(&mut self, name: &LocalName, frame_idx: usize) -> CompileResult<Option<UpvalueIndex>> {
484        {
485            let (current_frame, enclosing_frame) = Self::get_frames_mut(&mut self.frames, frame_idx);
486            
487            // check if the upvalue already exists in the current frame
488            if let Some(upval) = current_frame.find_upval(name) {
489                return Ok(Some(upval.index));
490            }
491            
492            // check if the local name exists in the enclosing scope
493            let enclosing = enclosing_frame.map_or(&mut self.toplevel, |frame| frame.scopes_mut());
494            if let Some(local) = enclosing.iter_nro_mut().find_map(|scope| scope.find_local_mut(name)) {
495                return Ok(Some(current_frame.create_upval_for_local(local)?.index));
496            }
497        }
498        
499        // check if an upvalue can be created in the enclosing scope to a local further down
500        if frame_idx > 0 {
501            if let Some(upval_idx) = self.resolve_upval_helper(name, frame_idx-1)? {
502                let (current_frame, enclosing_frame) = Self::get_frames_mut(&mut self.frames, frame_idx);
503                if let Some(enclosing_frame) = enclosing_frame {
504                    let upval = &enclosing_frame.upvalues()[usize::from(upval_idx)];
505                    
506                    return Ok(Some(current_frame.create_upval_for_upval(upval)?.index));
507                }
508            }
509        }
510        
511        Ok(None)
512    }
513    
514    // helper to get a frame by index and its enclosing frame
515    fn get_frames_mut(frames: &mut [CallFrame], frame_idx: usize) -> (&mut CallFrame, Option<&mut CallFrame>) {
516        let (frames, _) = frames.split_at_mut(frame_idx + 1);
517        let (current_frame, frames) = frames.split_last_mut().unwrap();
518        let enclosing_frame = frames.split_last_mut().map(|(last, _)| last);
519        (current_frame, enclosing_frame)
520    }
521    
522    // control flow
523    
524    // search for a scope that matches the given control flow and label
525    pub(super) fn resolve_control_flow(&self, target: ControlFlowTarget) -> Option<&Scope> {
526        self.local_scopes()
527            .iter_nro()
528            .find_map(|scope| {
529                if scope.tag().accepts_control_flow(target) {
530                    if target.label().is_none() || target.label() == scope.control_flow.label.as_ref() {
531                        return Some(scope)
532                    }
533                }
534                None
535            })
536    }
537    
538    pub(super) fn iter_scopes(&self) -> impl Iterator<Item=&Scope> {
539        self.local_scopes().iter_nro()
540    }
541    
542    pub(super) fn iter_scopes_mut(&mut self) -> impl Iterator<Item=&mut Scope> {
543        self.local_scopes_mut().iter_nro_mut()
544    }
545}