File: System\Linq\Expressions\Interpreter\LocalVariables.cs
Web Access
Project: src\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
 
namespace System.Linq.Expressions.Interpreter
{
    internal sealed class LocalVariable
    {
        private const int IsBoxedFlag = 1;
        private const int InClosureFlag = 2;
 
        public readonly int Index;
        private int _flags;
 
        public bool IsBoxed
        {
            get { return (_flags & IsBoxedFlag) != 0; }
            set
            {
                if (value)
                {
                    _flags |= IsBoxedFlag;
                }
                else
                {
                    _flags &= ~IsBoxedFlag;
                }
            }
        }
 
        public bool InClosure => (_flags & InClosureFlag) != 0;
 
        internal LocalVariable(int index, bool closure)
        {
            Index = index;
            _flags = (closure ? InClosureFlag : 0);
        }
 
        public override string ToString() =>
            string.Create(CultureInfo.InvariantCulture, $"{Index}: {(IsBoxed ? "boxed" : null)} {(InClosure ? "in closure" : null)}");
    }
 
    internal readonly struct LocalDefinition : IEquatable<LocalDefinition>
    {
        internal LocalDefinition(int localIndex, ParameterExpression parameter)
        {
            Index = localIndex;
            Parameter = parameter;
        }
 
        public int Index { get; }
 
        public ParameterExpression Parameter { get; }
 
        public override bool Equals([NotNullWhen(true)] object? obj) => obj is LocalDefinition other && Equals(other);
 
        public bool Equals(LocalDefinition other) => other.Index == Index && other.Parameter == Parameter;
 
        public override int GetHashCode() => Parameter is null ? 0 : Parameter.GetHashCode() ^ Index.GetHashCode();
    }
 
    internal sealed class LocalVariables
    {
        private readonly HybridReferenceDictionary<ParameterExpression, VariableScope> _variables = new HybridReferenceDictionary<ParameterExpression, VariableScope>();
        private Dictionary<ParameterExpression, LocalVariable>? _closureVariables;
 
        private int _localCount, _maxLocalCount;
 
        public LocalDefinition DefineLocal(ParameterExpression variable, int start)
        {
            var result = new LocalVariable(_localCount++, closure: false);
            _maxLocalCount = Math.Max(_localCount, _maxLocalCount);
 
            VariableScope? existing, newScope;
            if (_variables.TryGetValue(variable, out existing))
            {
                newScope = new VariableScope(result, start, existing);
                existing.ChildScopes ??= new List<VariableScope>();
                existing.ChildScopes.Add(newScope);
            }
            else
            {
                newScope = new VariableScope(result, start, parent: null);
            }
 
            _variables[variable] = newScope;
            return new LocalDefinition(result.Index, variable);
        }
 
        public void UndefineLocal(LocalDefinition definition, int end)
        {
            VariableScope scope = _variables[definition.Parameter];
            scope.Stop = end;
            if (scope.Parent != null)
            {
                _variables[definition.Parameter] = scope.Parent;
            }
            else
            {
                _variables.Remove(definition.Parameter);
            }
 
            _localCount--;
        }
 
        internal void Box(ParameterExpression variable, InstructionList instructions)
        {
            VariableScope scope = _variables[variable];
 
            LocalVariable local = scope.Variable;
            Debug.Assert(!local.IsBoxed && !local.InClosure);
            _variables[variable].Variable.IsBoxed = true;
 
            int curChild = 0;
            for (int i = scope.Start; i < scope.Stop && i < instructions.Count; i++)
            {
                if (scope.ChildScopes != null && scope.ChildScopes[curChild].Start == i)
                {
                    // skip boxing in the child scope
                    VariableScope child = scope.ChildScopes[curChild];
                    i = child.Stop;
 
                    curChild++;
                    continue;
                }
 
                instructions.SwitchToBoxed(local.Index, i);
            }
        }
 
        public int LocalCount => _maxLocalCount;
 
        public bool TryGetLocalOrClosure(ParameterExpression var, [NotNullWhen(true)] out LocalVariable? local)
        {
            if (_variables.TryGetValue(var, out VariableScope? scope))
            {
                local = scope.Variable;
                return true;
            }
            if (_closureVariables != null && _closureVariables.TryGetValue(var, out local))
            {
                return true;
            }
 
            local = null;
            return false;
        }
 
        /// <summary>
        /// Gets the variables which are defined in an outer scope and available within the current scope.
        /// </summary>
        internal Dictionary<ParameterExpression, LocalVariable>? ClosureVariables => _closureVariables;
 
        internal LocalVariable AddClosureVariable(ParameterExpression variable)
        {
            _closureVariables ??= new Dictionary<ParameterExpression, LocalVariable>();
            LocalVariable result = new LocalVariable(_closureVariables.Count, true);
            _closureVariables.Add(variable, result);
            return result;
        }
 
        /// <summary>
        /// Tracks where a variable is defined and what range of instructions it's used in.
        /// </summary>
        private sealed class VariableScope
        {
            public readonly int Start;
            public int Stop = int.MaxValue;
            public readonly LocalVariable Variable;
            public readonly VariableScope? Parent;
            public List<VariableScope>? ChildScopes;
 
            public VariableScope(LocalVariable variable, int start, VariableScope? parent)
            {
                Variable = variable;
                Start = start;
                Parent = parent;
            }
        }
    }
}