| File: PortableCallHelpers\InteropSignature.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 System.Globalization; using ILCompiler.DependencyAnalysis.Wasm; using Internal.JitInterface; using Internal.TypeSystem; namespace ILCompiler.PortableCallHelpers { /// <summary> /// Thrown when a signature token has no representation in the generated C. /// </summary> internal sealed class InvalidSignatureCharException(char c) : Exception($"Can't handle signature '{c}'") { public char Char { get; } = c; } /// <summary> /// Maps between the wasm signature strings produced by <see cref="WasmLowering"/> and the C /// declarations the generated interop files are built from. /// </summary> /// <remarks> /// The signature string format is documented in docs/design/coreclr/botr/readytorun-format.md /// (section "Wasm Signature String Encoding"). <see cref="WasmLowering"/> is the single /// implementation of the encoding; everything here either produces a string through it or /// consumes one it produced. /// </remarks> internal static class InteropSignature { /// <summary> /// Returns the wasm signature string for a method. /// </summary> /// <param name="method">The method to encode.</param> /// <param name="flags"> /// The default lowers as a managed method, taking the leading 'T' for an instance method /// and the trailing 'p' for the portable entry point argument; /// <see cref="WasmLowering.LoweringFlags.IsUnmanagedCallersOnly"/> lowers as a native function. /// </param> public static string GetMethodSignature(MethodDesc method, WasmLowering.LoweringFlags flags = WasmLowering.LoweringFlags.None) => WasmLowering.GetSignature(method.Signature, flags).SignatureString; /// <summary> /// Gets the signature encoding for a type in parameter position: a primitive character /// (<c>i</c>, <c>l</c>, <c>f</c>, <c>d</c>, <c>V</c>), a multi-slot token, or /// <c>S<size></c>/<c>A<size></c> for a struct that is passed by reference. /// </summary> public static string GetAbiToken(TypeDesc type) { if (type.IsVoid) return "v"; TypeDesc loweredType = WasmLowering.LowerToAbiType(type); if (loweredType is null) { // WasmLowering.GetSignature splits this case in two, and both have to be mirrored // here or a type gets one token in a method signature and a different one at the // interop boundary. Multi-segment types come first: they travel by value across // several wasm parameters, so calling them by-reference structs would both hide // them from the multi-slot rejection and mis-declare them in C. if (WasmLowering.TryGetMultiSegmentLayout(type, out WasmValueType slotType, out int slotCount)) return string.Create(CultureInfo.InvariantCulture, $"{WasmLowering.WasmValueTypeToSigChar(slotType)}{slotCount}"); // Passed by reference; the size is what the callee needs to know. 'A' marks a struct // whose alignment exceeds a stack slot, matching what WasmLowering.GetSignature emits // so a type gets the same token here as it does inside a method signature. Debug.Assert(type is DefType, "LowerToAbiType only returns null for aggregates"); char kind = CompilerTypeSystemContext.GetClassAlignmentRequirementStatic((DefType)type) > 8 ? 'A' : 'S'; return string.Create(CultureInfo.InvariantCulture, $"{kind}{type.GetElementSize().AsInt}"); } return WasmLowering.WasmValueTypeToSigChar(WasmLowering.LowerType(loweredType)).ToString(); } /// <summary> /// Parses a signature string into individual tokens. Single-char types produce one-char /// tokens; struct encodings produce multi-char tokens like "S8" or "A32", and a multi-slot /// parameter produces a two-char token like "l2" or "V4". The 'a' and 'p' suffixes are /// included as their own tokens. /// </summary> public static List<string> ParseSignatureTokens(string signature) { List<string> tokens = []; int i = 0; while (i < signature.Length) { if (signature[i] is 'S' or 'A') { int start = i; i++; // skip 'S'/'A' while (i < signature.Length && char.IsDigit(signature[i])) i++; tokens.Add(signature.Substring(start, i - start)); } else if (signature[i] is 'l' or 'V' && i + 1 < signature.Length && char.IsDigit(signature[i + 1])) { tokens.Add(signature.Substring(i, 2)); i += 2; } else { tokens.Add(signature[i].ToString()); i++; } } return tokens; } /// <summary> /// True for a token describing a type passed by value across several wasm parameters /// ("l2", "V2", "V4"). Interop signatures do not use these today. /// </summary> public static bool IsMultiSlotToken(string token) => token.Length == 2 && token[0] is 'l' or 'V' && char.IsDigit(token[1]); private static void RejectMultiSlotToken(string token) { if (IsMultiSlotToken(token)) throw new LogAsErrorException($"Multi-slot signature token '{token}' is not supported in interop thunks"); } public static string TokenToNativeType(string token) { RejectMultiSlotToken(token); return token[0] switch { 'v' => "void", 'i' => "int32_t", 'l' => "int64_t", 'f' => "float", 'd' => "double", 'S' or 'A' or 'T' => "int32_t", 'p' => "PCODE", _ => throw new InvalidSignatureCharException(token[0]) }; } public static string TokenToNameType(string token) { RejectMultiSlotToken(token); return token[0] switch { 'v' => "Void", 'i' => "I32", 'l' => "I64", 'f' => "F32", 'd' => "F64", 'S' or 'A' => token, 'T' => "This", 'p' => "PE", _ => throw new InvalidSignatureCharException(token[0]) }; } public static string TokenToArgType(string token) { RejectMultiSlotToken(token); return token[0] switch { 'i' or 'T' => "ARG_I32", 'l' => "ARG_I64", 'f' => "ARG_F32", 'd' => "ARG_F64", 'S' or 'A' => "ARG_IND", _ => throw new InvalidSignatureCharException(token[0]) }; } /// <summary> /// Returns the number of INTERP_STACK_SLOT_SIZE slots consumed by a token. Struct tokens /// consume max((size + 7) / 8, 1) slots; all others consume 1. /// </summary> public static int TokenToSlotCount(string token) { if (token[0] is not ('S' or 'A') || token.Length < 2) return 1; return Math.Max((GetStructSize(token) + 7) / 8, 1); } public static int GetStructSize(string token) => int.Parse(token.AsSpan(1), CultureInfo.InvariantCulture); } }