File: Compiler\DeferredWasmCallFinalizationPlan.cs
Web Access
Project: ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;

using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysis.ReadyToRun;

using Internal.TypeSystem;

namespace ILCompiler
{
    // Order deferred-call finalization using the emitted call graph. Recursive
    // components share a level; their callees occupy lower levels. The eager
    // subset includes cold callees, while the non-eager remainder is releveled
    // after eager methods become available. See #DeferredWasmCallFinalization.
    internal sealed class DeferredWasmCallFinalizationPlan
    {
        private readonly MethodDesc[][] _methodsByLevel;
        private readonly MethodDesc[] _methods;
        private readonly int[][] _adjacency;

        private DeferredWasmCallFinalizationPlan(MethodDesc[] methods, int[][] adjacency, bool includeColdCalls)
        {
            _methods = methods;
            _adjacency = adjacency;
            _methodsByLevel = ComputeMethodsByLevel(methods, adjacency, includeColdCalls);
        }

        internal int LevelCount => _methodsByLevel.Length;

        internal IReadOnlyList<MethodDesc> GetMethodsAtLevel(int level)
        {
            return _methodsByLevel[level];
        }

        internal DeferredWasmCallFinalizationPlan CreateEagerPlan(HashSet<MethodDesc> roots)
        {
            bool[] included = new bool[_methods.Length];
            var pending = new Stack<int>();
            for (int i = 0; i < _methods.Length; i++)
            {
                if (roots.Contains(_methods[i]))
                {
                    pending.Push(i);
                }
            }

            while (pending.TryPop(out int method))
            {
                if (included[method])
                {
                    continue;
                }

                included[method] = true;
                foreach (int callee in _adjacency[method])
                {
                    pending.Push(callee);
                }
            }

            return CreateSubset(included, includeColdCalls: true);
        }

        internal DeferredWasmCallFinalizationPlan CreateNonEagerPlan(HashSet<MethodDesc> finalizedEagerMethods)
        {
            bool[] included = new bool[_methods.Length];
            for (int i = 0; i < _methods.Length; i++)
            {
                included[i] = !finalizedEagerMethods.Contains(_methods[i]);
            }

            return CreateSubset(included, includeColdCalls: false);
        }

        private DeferredWasmCallFinalizationPlan CreateSubset(bool[] included, bool includeColdCalls)
        {
            var methods = new List<MethodDesc>();
            int[] indices = new int[_methods.Length];
            Array.Fill(indices, -1);
            for (int i = 0; i < _methods.Length; i++)
            {
                if (included[i])
                {
                    indices[i] = methods.Count;
                    methods.Add(_methods[i]);
                }
            }

            int[][] adjacency = new int[methods.Count][];
            for (int i = 0; i < _methods.Length; i++)
            {
                if (!included[i])
                {
                    continue;
                }

                var callees = new List<int>();
                foreach (int callee in _adjacency[i])
                {
                    if (included[callee])
                    {
                        callees.Add(indices[callee]);
                    }
                }

                adjacency[indices[i]] = callees.ToArray();
            }

            return new DeferredWasmCallFinalizationPlan(methods.ToArray(), adjacency, includeColdCalls);
        }

        internal static int[] ComputeLevelsForTest(int[][] adjacency)
        {
            int[] components = ComputeStronglyConnectedComponents(adjacency, out int[] componentLevels);
            int[] levels = new int[adjacency.Length];
            for (int i = 0; i < levels.Length; i++)
            {
                levels[i] = componentLevels[components[i]];
            }

            return levels;
        }

        internal static DeferredWasmCallFinalizationPlan Create(List<MethodWithGCInfo> methodNodes, NodeFactory factory)
        {
            methodNodes.Sort(static (left, right) => TypeSystemComparer.Instance.Compare(left.Method, right.Method));

            MethodDesc[] methods = new MethodDesc[methodNodes.Count];
            var methodIndices = new Dictionary<MethodDesc, int>(methods.Length);
            for (int i = 0; i < methods.Length; i++)
            {
                methods[i] = methodNodes[i].Method;
                methodIndices.Add(methods[i], i);
            }

            int[][] adjacency = new int[methods.Length][];
            var calleeIndices = new HashSet<int>();
            for (int i = 0; i < methods.Length; i++)
            {
                calleeIndices.Clear();
                foreach (Relocation relocation in methodNodes[i].GetData(factory, relocsOnly: false).Relocs.AsSpan())
                {
                    if (relocation.RelocType != RelocType.WASM_FUNCTION_INDEX_LEB)
                    {
                        continue;
                    }

                    MethodWithGCInfo target = relocation.Target switch
                    {
                        DeferredWasmCallNode call => call.Target,
                        MethodWithGCInfo method => method,
                        _ => null,
                    };
                    if (target is not null && methodIndices.TryGetValue(target.Method, out int calleeIndex))
                    {
                        calleeIndices.Add(calleeIndex);
                    }
                }

                int[] callees = calleeIndices.Count == 0 ? Array.Empty<int>() : new int[calleeIndices.Count];
                calleeIndices.CopyTo(callees);
                Array.Sort(callees);
                adjacency[i] = callees;
            }

            return new DeferredWasmCallFinalizationPlan(methods, adjacency, includeColdCalls: false);
        }

        private static MethodDesc[][] ComputeMethodsByLevel(MethodDesc[] methods, int[][] adjacency, bool includeColdCalls)
        {
            if (!includeColdCalls)
            {
                int[][] hotAdjacency = new int[adjacency.Length][];
                for (int i = 0; i < adjacency.Length; i++)
                {
                    var callees = new List<int>();
                    foreach (int callee in adjacency[i])
                    {
                        if (!methods[callee].IsNoInlining && !methods[callee].IsNoOptimization)
                        {
                            callees.Add(callee);
                        }
                    }

                    hotAdjacency[i] = callees.ToArray();
                }

                adjacency = hotAdjacency;
            }

            int[] components = ComputeStronglyConnectedComponents(adjacency, out int[] componentLevels);

            int maxLevel = 0;
            for (int i = 0; i < componentLevels.Length; i++)
            {
                maxLevel = Math.Max(maxLevel, componentLevels[i]);
            }

            var methodsByLevel = new List<MethodDesc>[maxLevel + 1];
            for (int i = 0; i < methodsByLevel.Length; i++)
            {
                methodsByLevel[i] = new List<MethodDesc>();
            }

            for (int i = 0; i < methods.Length; i++)
            {
                int level = componentLevels[components[i]];
                methodsByLevel[level].Add(methods[i]);
            }

            var resultLevels = new MethodDesc[methodsByLevel.Length][];
            for (int i = 0; i < methodsByLevel.Length; i++)
            {
                resultLevels[i] = methodsByLevel[i].ToArray();
            }

            return resultLevels;
        }

        // Iterative Tarjan traversal avoids compiler-stack exhaustion on long call chains.
        // traversal holds DFS frames, with NextEdge recording where each caller resumes;
        // inProgress holds vertices awaiting SCC assignment, which can outlive their DFS frames.
        // Discovery indices never change. An edge to an active vertex lowers the current low
        // link using that vertex's discovery index; returning from a DFS child instead propagates
        // the child's low link. Equality of a low link and discovery index identifies an SCC root.
        // Traversal and level computation together use O(V + E) time and O(V) workspace.
        private static int[] ComputeStronglyConnectedComponents(int[][] adjacency, out int[] componentLevels)
        {
            int currentIndex = 0;
            int[] indices = new int[adjacency.Length];
            int[] lowLinks = new int[adjacency.Length];
            int[] components = new int[adjacency.Length];
            bool[] onStack = new bool[adjacency.Length];
            Array.Fill(indices, -1);
            Array.Fill(components, -1);

            var inProgress = new Stack<int>();
            var traversal = new List<TraversalFrame>();
            var levels = new List<int>();
            var members = new List<int>();

            for (int root = 0; root < adjacency.Length; root++)
            {
                if (indices[root] != -1)
                {
                    continue;
                }

                InitializeVertex(root);
                traversal.Add(new TraversalFrame(root));

                while (traversal.Count > 0)
                {
                    int frameIndex = traversal.Count - 1;
                    TraversalFrame frame = traversal[frameIndex];
                    int vertex = frame.Vertex;

                    if (frame.NextEdge < adjacency[vertex].Length)
                    {
                        int destination = adjacency[vertex][frame.NextEdge];
                        frame.NextEdge++;
                        traversal[frameIndex] = frame;

                        if (indices[destination] == -1)
                        {
                            InitializeVertex(destination);
                            traversal.Add(new TraversalFrame(destination));
                        }
                        else if (onStack[destination])
                        {
                            lowLinks[vertex] = Math.Min(lowLinks[vertex], indices[destination]);
                        }

                        continue;
                    }

                    traversal.RemoveAt(frameIndex);
                    if (traversal.Count > 0)
                    {
                        int parent = traversal[traversal.Count - 1].Vertex;
                        lowLinks[parent] = Math.Min(lowLinks[parent], lowLinks[vertex]);
                    }

                    if (lowLinks[vertex] == indices[vertex])
                    {
                        int component = levels.Count;
                        members.Clear();
                        int componentVertex;
                        do
                        {
                            componentVertex = inProgress.Pop();
                            onStack[componentVertex] = false;
                            components[componentVertex] = component;
                            members.Add(componentVertex);
                        }
                        while (componentVertex != vertex);

                        // Tarjan completes external callees before callers. Label every member
                        // before scanning edges so internal calls do not contribute to the level.
                        int level = 0;
                        foreach (int member in members)
                        {
                            foreach (int callee in adjacency[member])
                            {
                                int calleeComponent = components[callee];
                                Debug.Assert(calleeComponent >= 0 && calleeComponent <= component);
                                if (calleeComponent != component)
                                {
                                    level = Math.Max(level, levels[calleeComponent] + 1);
                                }
                            }
                        }
                        levels.Add(level);
                    }
                }
            }

            componentLevels = levels.ToArray();
            return components;

            void InitializeVertex(int vertex)
            {
                indices[vertex] = currentIndex;
                lowLinks[vertex] = currentIndex;
                currentIndex++;
                inProgress.Push(vertex);
                onStack[vertex] = true;
            }
        }

        private struct TraversalFrame
        {
            public TraversalFrame(int vertex)
            {
                Vertex = vertex;
                NextEdge = 0;
            }

            public int Vertex;
            public int NextEdge;
        }
    }
}