File: Compiler\DependencyAnalysis\ReadyToRun\WasmThunkArgLayout.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.Collections.Generic;
using System.Diagnostics;
using ILCompiler.DependencyAnalysis.Wasm;
using Internal.CallingConvention;
using Internal.JitInterface;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis.ReadyToRun;

internal enum WasmThunkArgKind
{
    This,
    RetBuf,
    // The signature encodes the generic context like any pointer-sized argument, so it is only recognizable
    // when the async continuation follows it; otherwise it occupies the first argument slot either way.
    GenericContext,
    AsyncContinuation,
    Argument,
}

/// <summary>
/// An argument of a Wasm thunk: the Wasm parameters it arrives in and its slot in the argument area.
/// </summary>
internal readonly struct WasmThunkArg
{
    public WasmThunkArg(WasmThunkArgKind kind, int offset, int wasmParamIndex, int wasmParamCount, WasmValueType wasmType, int indirectStructSize)
    {
        Kind = kind;
        Offset = offset;
        WasmParamIndex = wasmParamIndex;
        WasmParamCount = wasmParamCount;
        WasmType = wasmType;
        IndirectStructSize = indirectStructSize;
    }

    public WasmThunkArgKind Kind { get; }

    /// <summary>Offset from the TransitionBlock base, or <see cref="TransitionBlock.InvalidOffset"/> for the return buffer.</summary>
    public int Offset { get; }

    /// <summary>Index of the first Wasm parameter in the thunk's function type.</summary>
    public int WasmParamIndex { get; }

    /// <summary>Number of Wasm parameters; 0 for an empty struct, more than 1 for a multi-slot value.</summary>
    public int WasmParamCount { get; }

    /// <summary>Type of each Wasm parameter.</summary>
    public WasmValueType WasmType { get; }

    /// <summary>Size of a struct passed by reference, otherwise 0.</summary>
    public int IndirectStructSize { get; }

    public bool IsIndirectStruct => IndirectStructSize != 0;
    public bool IsEmptyStruct => WasmParamCount == 0;
    public bool IsMultiSlot => WasmParamCount > 1;
}

/// <summary>
/// Maps each element of a managed Wasm signature, in Wasm parameter order, to its location in the
/// ArgIterator argument area.
/// </summary>
internal sealed class WasmThunkArgLayout
{
    public TransitionBlock TransitionBlock { get; }
    public int SizeOfFrameArgumentArray { get; }
    public WasmThunkArg[] Args { get; }
    public int PortableEntrypointParamIndex { get; }

    public WasmThunkArgLayout(WasmSignature wasmSignature, TypeSystemContext context)
    {
        (MethodSignature signature, ArgIterator<TypeHandle> argit, TransitionBlock transitionBlock) = BuildArgIterator(wasmSignature, context);
        TransitionBlock = transitionBlock;
        SizeOfFrameArgumentArray = argit.SizeOfFrameArgumentArray();

        string sig = wasmSignature.SignatureString;
        WasmResultType wasmParams = wasmSignature.FuncType.Params;
        List<WasmThunkArg> args = new List<WasmThunkArg>();
        int wasmParamIndex = 1; // 0 is $sp

        void AddHiddenArg(WasmThunkArgKind kind, int offset)
        {
            args.Add(new WasmThunkArg(kind, offset, wasmParamIndex, 1, wasmParams.Types[wasmParamIndex], 0));
            wasmParamIndex++;
        }

        int pos = 0;
        bool hasRetBuf = sig[pos] == 'S';
        if (hasRetBuf)
        {
            WasmLowering.ParseStructSize(sig, ref pos);
        }
        else
        {
            pos++;
        }

        if (sig[pos] == 'T')
        {
            Debug.Assert(argit.HasThis);
            AddHiddenArg(WasmThunkArgKind.This, transitionBlock.ThisOffset);
            pos++;
        }

        if (hasRetBuf)
        {
            AddHiddenArg(WasmThunkArgKind.RetBuf, TransitionBlock.InvalidOffset);
        }

        if (argit.HasParamType)
        {
            Debug.Assert(sig[pos] == ((context.Target.PointerSize == 4) ? 'i' : 'l'));
            AddHiddenArg(WasmThunkArgKind.GenericContext, argit.GetParamTypeArgOffset());
            pos++;
        }

        if (sig[pos] == 'a')
        {
            Debug.Assert(argit.HasAsyncContinuation);
            AddHiddenArg(WasmThunkArgKind.AsyncContinuation, argit.GetAsyncContinuationArgOffset());
            pos++;
        }

        for (int i = 0; sig[pos] != 'p'; i++)
        {
            int offset = argit.GetNextOffset();
            TypeDesc type = signature[i];
            bool isIndirectStruct = WasmLowering.CurrentArgLowersValueTypeToPassAsByref(argit);
            char c = sig[pos];

            int indirectStructSize = 0;
            int wasmParamCount = 1;
            if (c == 'e')
            {
                Debug.Assert(WasmLowering.IsEmptyStruct(type));
                wasmParamCount = 0;
                pos++;
            }
            else if (c is 'S' or 'A')
            {
                indirectStructSize = WasmLowering.ParseStructSize(sig, ref pos);
                Debug.Assert(indirectStructSize == type.GetElementSize().AsInt);
            }
            else if ((c is 'l' or 'V') && char.IsDigit(sig[pos + 1]))
            {
                wasmParamCount = sig[pos + 1] - '0';
                Debug.Assert(WasmLowering.TryGetMultiSegmentLayout(type, out WasmValueType slotType, out int slotCount) &&
                    (slotType == wasmParams.Types[wasmParamIndex]) && (slotCount == wasmParamCount));
                pos += 2;
            }
            else
            {
                pos++;
            }

            Debug.Assert(isIndirectStruct == (indirectStructSize != 0));
            WasmValueType wasmType = (wasmParamCount != 0) ? wasmParams.Types[wasmParamIndex] : default;
            args.Add(new WasmThunkArg(WasmThunkArgKind.Argument, offset, wasmParamIndex, wasmParamCount, wasmType, indirectStructSize));
            wasmParamIndex += wasmParamCount;
        }

        Debug.Assert(argit.GetNextOffset() == TransitionBlock.InvalidOffset);
        Debug.Assert((pos == sig.Length - 1) && (wasmParamIndex == wasmParams.Types.Length - 1),
            $"Wasm thunk argument layout does not cover the parameters of '{sig}'");

        Args = args.ToArray();
        PortableEntrypointParamIndex = wasmParamIndex;
    }

    /// <summary>
    /// Builds the ArgIterator for a Wasm thunk from its Wasm signature.
    /// </summary>
    private static (MethodSignature, ArgIterator<TypeHandle>, TransitionBlock) BuildArgIterator(WasmSignature wasmSignature, TypeSystemContext context)
    {
        MethodSignature signature = WasmLowering.RaiseSignature(wasmSignature, context);
        bool isAsyncCall = wasmSignature.SignatureString.Contains('a');
        bool hasGenericContext = WasmLowering.HasGenericContextBeforeAsync(wasmSignature, context);
        if (hasGenericContext)
        {
            // RaiseSignature returns the generic context as the first parameter; lay it out as the hidden
            // instantiation argument instead, so it precedes the async continuation.
            TypeDesc[] parameters = new TypeDesc[signature.Length - 1];
            for (int i = 0; i < parameters.Length; i++)
            {
                parameters[i] = signature[i + 1];
            }

            signature = new MethodSignature(signature.Flags, signature.GenericParameterCount, signature.ReturnType, parameters);
        }

        (ArgIterator<TypeHandle> argit, TransitionBlock transitionBlock) = GCRefMapBuilder.BuildArgIterator(signature, context,
            methodRequiresInstArg: hasGenericContext,
            methodIsAsyncCall: isAsyncCall);

        return (signature, argit, transitionBlock);
    }

}