File: DataFlow\InterproceduralState.cs
Web Access
Project: src\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer)
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared.DataFlow;
 
namespace ILLink.RoslynAnalyzer.DataFlow
{
    // Tracks the set of methods which get analyzed together during interprocedural analysis,
    // and the possible states of hoisted locals in state machine methods and lambdas/local functions.
    public struct InterproceduralState<TValue, TValueLattice> : IEquatable<InterproceduralState<TValue, TValueLattice>>
        where TValue : struct, IEquatable<TValue>
        where TValueLattice : ILattice<TValue>
    {
        public ValueSet<MethodBodyValue> Methods;
 
        // The HoistedLocals dictionary has a default value of MaybeLattice.Top (effectively null),
        // for any local that has not been discovered to be captured by a nested function.
        // Once we discover that a local is captured, it gets the value TValueLattice.Top
        // (in our case the "empty" MultiValue), and from then on reading/writing the local will use this
        // dictionary instead of the per-method dictionary.
        public DefaultValueDictionary<LocalKey, Maybe<TValue>> HoistedLocals;
 
        private readonly InterproceduralStateLattice<TValue, TValueLattice> lattice;
 
        public InterproceduralState(
            ValueSet<MethodBodyValue> methods,
            DefaultValueDictionary<LocalKey, Maybe<TValue>> hoistedLocals,
            InterproceduralStateLattice<TValue, TValueLattice> lattice)
        {
            Methods = methods;
            HoistedLocals = hoistedLocals;
            this.lattice = lattice;
        }
 
        public bool Equals(InterproceduralState<TValue, TValueLattice> other)
            => Methods.Equals(other.Methods) && HoistedLocals.Equals(other.HoistedLocals);
 
        public override bool Equals(object obj)
            => obj is InterproceduralState<TValue, TValueLattice> inst && Equals(inst);
 
        public override int GetHashCode()
            => throw new NotImplementedException();
 
        public InterproceduralState<TValue, TValueLattice> Clone()
            => new(Methods.DeepCopy(),
            HoistedLocals.Clone(), lattice);
 
        public void TrackMethod(MethodBodyValue method)
        {
            Debug.Assert(!Methods.IsUnknown());
            var methodsList = new List<MethodBodyValue>(Methods.GetKnownValues());
            methodsList.Add(method);
            Methods = new ValueSet<MethodBodyValue>(methodsList);
        }
 
        public void TrackHoistedLocal(LocalKey key)
        {
            var existingValue = HoistedLocals.Get(key);
            if (existingValue.MaybeValue != null)
                return; // Already tracked
 
            HoistedLocals.Set(key, new Maybe<TValue>(lattice.HoistedLocalLattice.ValueLattice.ValueLattice.Top));
        }
 
        public bool TrySetHoistedLocal(LocalKey key, TValue value)
        {
            var existingValue = HoistedLocals.Get(key);
            if (existingValue.MaybeValue == null)
                return false;
 
            // For hoisted locals, we track the entire set of assigned values seen
            // in the closure of a method, so setting a hoisted local value meets
            // it with any existing value.
            HoistedLocals.Set(key,
                lattice.HoistedLocalLattice.ValueLattice.Meet(
                    existingValue, new(value)));
            return true;
        }
 
        public bool TryGetHoistedLocal(LocalKey key, [NotNullWhen(true)] out TValue? value)
            => (value = HoistedLocals.Get(key).MaybeValue) != null;
    }
 
    public struct InterproceduralStateLattice<TValue, TValueLattice> : ILattice<InterproceduralState<TValue, TValueLattice>>
        where TValue : struct, IEquatable<TValue>
        where TValueLattice : ILattice<TValue>
    {
        public readonly ValueSetLattice<MethodBodyValue> MethodLattice;
 
        public readonly DictionaryLattice<LocalKey, Maybe<TValue>, MaybeLattice<TValue, TValueLattice>> HoistedLocalLattice;
 
        public InterproceduralStateLattice(
            ValueSetLattice<MethodBodyValue> methodLattice,
            DictionaryLattice<LocalKey, Maybe<TValue>, MaybeLattice<TValue, TValueLattice>> hoistedLocalLattice
        )
        {
            MethodLattice = methodLattice;
            HoistedLocalLattice = hoistedLocalLattice;
        }
 
        public InterproceduralState<TValue, TValueLattice> Top => new(MethodLattice.Top,
            HoistedLocalLattice.Top, this);
 
        public InterproceduralState<TValue, TValueLattice> Meet(InterproceduralState<TValue, TValueLattice> left, InterproceduralState<TValue, TValueLattice> right)
            => new(
                MethodLattice.Meet(left.Methods, right.Methods),
                HoistedLocalLattice.Meet(left.HoistedLocals, right.HoistedLocals),
                this);
    }
}