|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext;
// Contains support for pseudo-methods on multidimensional arrays.
//
// Opcodes such as newarr, ldelem, ldelema, stelem do not work with
// multidimensional arrays and same functionality is available in
// a form of well known pseudo-methods "Get", "Set", "Address" and ".ctor"
//
//=========================
//
// 14.2 Arrays (From partition II) -
//The class that the VES creates for arrays contains several methods whose implementation is supplied by the
//VES:
//
//* A constructor that takes a sequence of int32 arguments, one for each dimension of the array, that specify
//the number of elements in each dimension beginning with the first dimension. A lower bound of zero is
//assumed.
//
//* A constructor that takes twice as many int32 arguments as there are dimensions of the array. These
//arguments occur in pairs—one pair per dimension—with the first argument of each pair specifying the
//lower bound for that dimension, and the second argument specifying the total number of elements in that
//dimension. Note that vectors are not created with this constructor, since a zero lower bound is assumed for
//vectors.
//
//* A Get method that takes a sequence of int32 arguments, one for each dimension of the array, and returns
//a value whose type is the element type of the array. This method is used to access a specific element of the
//array where the arguments specify the index into each dimension, beginning with the first, of the element
//to be returned.
//
//* A Set method that takes a sequence of int32 arguments, one for each dimension of the array, followed by
//a value whose type is the element type of the array. The return type of Set is void. This method is used to
//set a specific element of the array where the arguments specify the index into each dimension, beginning
//with the first, of the element to be set and the final argument specifies the value to be stored into the target
//element.
//
//* An Address method that takes a sequence of int32 arguments, one for each dimension of the array, and
//has a return type that is a managed pointer to the array's element type. This method is used to return a
//managed pointer to a specific element of the array where the arguments specify the index into each
//dimension, beginning with the first, of the element whose address is to be returned.
namespace Microsoft.CodeAnalysis.CodeGen
{
/// <summary>
/// Constructs and caches already created pseudo-methods.
/// Every compiled module is supposed to have one of this, created lazily
/// (multidimensional arrays are not common).
/// </summary>
internal class ArrayMethods
{
// There are four kinds of array pseudo-methods
// They are specific to a given array type
private enum ArrayMethodKind : byte
{
GET,
SET,
ADDRESS,
CTOR,
}
/// <summary>
/// Acquires an array constructor for a given array type
/// </summary>
public ArrayMethod GetArrayConstructor(Cci.IArrayTypeReference arrayType)
{
return GetArrayMethod(arrayType, ArrayMethodKind.CTOR);
}
/// <summary>
/// Acquires an element getter method for a given array type
/// </summary>
public ArrayMethod GetArrayGet(Cci.IArrayTypeReference arrayType)
=> GetArrayMethod(arrayType, ArrayMethodKind.GET);
/// <summary>
/// Acquires an element setter method for a given array type
/// </summary>
public ArrayMethod GetArraySet(Cci.IArrayTypeReference arrayType)
=> GetArrayMethod(arrayType, ArrayMethodKind.SET);
/// <summary>
/// Acquires an element referencer method for a given array type
/// </summary>
public ArrayMethod GetArrayAddress(Cci.IArrayTypeReference arrayType)
=> GetArrayMethod(arrayType, ArrayMethodKind.ADDRESS);
/// <summary>
/// Maps {array type, method kind} tuples to implementing pseudo-methods.
/// </summary>
private readonly ConcurrentDictionary<(byte methodKind, IReferenceOrISignature arrayType), ArrayMethod> _dict =
new ConcurrentDictionary<(byte, IReferenceOrISignature), ArrayMethod>();
/// <summary>
/// lazily fetches or creates a new array method.
/// </summary>
private ArrayMethod GetArrayMethod(Cci.IArrayTypeReference arrayType, ArrayMethodKind id)
{
var key = ((byte)id, new IReferenceOrISignature(arrayType));
ArrayMethod? result;
var dict = _dict;
if (!dict.TryGetValue(key, out result))
{
result = MakeArrayMethod(arrayType, id);
result = dict.GetOrAdd(key, result);
}
return result;
}
private static ArrayMethod MakeArrayMethod(Cci.IArrayTypeReference arrayType, ArrayMethodKind id)
{
switch (id)
{
case ArrayMethodKind.CTOR:
return new ArrayConstructor(arrayType);
case ArrayMethodKind.GET:
return new ArrayGet(arrayType);
case ArrayMethodKind.SET:
return new ArraySet(arrayType);
case ArrayMethodKind.ADDRESS:
return new ArrayAddress(arrayType);
}
throw ExceptionUtilities.UnexpectedValue(id);
}
/// <summary>
/// "newobj ArrayConstructor" is equivalent of "newarr ElementType"
/// when working with multidimensional arrays
/// </summary>
private sealed class ArrayConstructor : ArrayMethod
{
public ArrayConstructor(Cci.IArrayTypeReference arrayType) : base(arrayType) { }
public override string Name => ".ctor";
public override Cci.ITypeReference GetType(EmitContext context)
=> context.Module.GetPlatformType(Cci.PlatformType.SystemVoid, context);
}
/// <summary>
/// "call ArrayGet" is equivalent of "ldelem ElementType"
/// when working with multidimensional arrays
/// </summary>
private sealed class ArrayGet : ArrayMethod
{
public ArrayGet(Cci.IArrayTypeReference arrayType) : base(arrayType) { }
public override string Name => "Get";
public override Cci.ITypeReference GetType(EmitContext context)
=> arrayType.GetElementType(context);
}
/// <summary>
/// "call ArrayAddress" is equivalent of "ldelema ElementType"
/// when working with multidimensional arrays
/// </summary>
private sealed class ArrayAddress : ArrayMethod
{
public ArrayAddress(Cci.IArrayTypeReference arrayType) : base(arrayType) { }
public override bool ReturnValueIsByRef => true;
public override Cci.ITypeReference GetType(EmitContext context)
=> arrayType.GetElementType(context);
public override string Name => "Address";
}
/// <summary>
/// "call ArraySet" is equivalent of "stelem ElementType"
/// when working with multidimensional arrays
/// </summary>
private sealed class ArraySet : ArrayMethod
{
public ArraySet(Cci.IArrayTypeReference arrayType) : base(arrayType) { }
public override string Name => "Set";
public override Cci.ITypeReference GetType(EmitContext context)
=> context.Module.GetPlatformType(Cci.PlatformType.SystemVoid, context);
protected override ImmutableArray<ArrayMethodParameterInfo> MakeParameters()
{
int rank = (int)arrayType.Rank;
var parameters = ArrayBuilder<ArrayMethodParameterInfo>.GetInstance(rank + 1);
for (int i = 0; i < rank; i++)
{
parameters.Add(ArrayMethodParameterInfo.GetIndexParameter((ushort)i));
}
parameters.Add(new ArraySetValueParameterInfo((ushort)rank, arrayType));
return parameters.ToImmutableAndFree();
}
}
}
/// <summary>
/// Represents a parameter in an array pseudo-method.
///
/// NOTE: It appears that only number of indices is used for verification,
/// types just have to be Int32.
/// Even though actual arguments can be native ints.
/// </summary>
internal class ArrayMethodParameterInfo : Cci.IParameterTypeInformation
{
// position in the signature
private readonly ushort _index;
// cache common parameter instances
// (we can do this since the only data we have is the index)
private static readonly ArrayMethodParameterInfo s_index0 = new ArrayMethodParameterInfo(0);
private static readonly ArrayMethodParameterInfo s_index1 = new ArrayMethodParameterInfo(1);
private static readonly ArrayMethodParameterInfo s_index2 = new ArrayMethodParameterInfo(2);
private static readonly ArrayMethodParameterInfo s_index3 = new ArrayMethodParameterInfo(3);
protected ArrayMethodParameterInfo(ushort index)
{
_index = index;
}
public static ArrayMethodParameterInfo GetIndexParameter(ushort index)
{
switch (index)
{
case 0: return s_index0;
case 1: return s_index1;
case 2: return s_index2;
case 3: return s_index3;
}
return new ArrayMethodParameterInfo(index);
}
public ImmutableArray<Cci.ICustomModifier> RefCustomModifiers
=> ImmutableArray<Cci.ICustomModifier>.Empty;
public ImmutableArray<Cci.ICustomModifier> CustomModifiers
=> ImmutableArray<Cci.ICustomModifier>.Empty;
public bool IsByReference => false;
public virtual Cci.ITypeReference GetType(EmitContext context)
=> context.Module.GetPlatformType(Cci.PlatformType.SystemInt32, context);
public ushort Index => _index;
}
/// <summary>
/// Represents the "value" parameter of the Set pseudo-method.
///
/// NOTE: unlike index parameters, type of the value parameter must match
/// the actual element type.
/// </summary>
internal sealed class ArraySetValueParameterInfo : ArrayMethodParameterInfo
{
private readonly Cci.IArrayTypeReference _arrayType;
internal ArraySetValueParameterInfo(ushort index, Cci.IArrayTypeReference arrayType)
: base(index)
{
_arrayType = arrayType;
}
public override Cci.ITypeReference GetType(EmitContext context)
=> _arrayType.GetElementType(context);
}
/// <summary>
/// Base of all array methods. They have a lot in common.
/// </summary>
internal abstract class ArrayMethod : Cci.IMethodReference
{
private readonly ImmutableArray<ArrayMethodParameterInfo> _parameters;
protected readonly Cci.IArrayTypeReference arrayType;
protected ArrayMethod(Cci.IArrayTypeReference arrayType)
{
this.arrayType = arrayType;
_parameters = MakeParameters();
}
public abstract string Name { get; }
public abstract Cci.ITypeReference GetType(EmitContext context);
// Address overrides this to "true"
public virtual bool ReturnValueIsByRef => false;
// Set overrides this to include "value" parameter.
protected virtual ImmutableArray<ArrayMethodParameterInfo> MakeParameters()
{
int rank = (int)arrayType.Rank;
var parameters = ArrayBuilder<ArrayMethodParameterInfo>.GetInstance(rank);
for (int i = 0; i < rank; i++)
{
parameters.Add(ArrayMethodParameterInfo.GetIndexParameter((ushort)i));
}
return parameters.ToImmutableAndFree();
}
public ImmutableArray<Cci.IParameterTypeInformation> GetParameters(EmitContext context)
=> StaticCast<Cci.IParameterTypeInformation>.From(_parameters);
public bool AcceptsExtraArguments => false;
public ushort GenericParameterCount => 0;
public Cci.IMethodDefinition? GetResolvedMethod(EmitContext context) => null;
public ImmutableArray<Cci.IParameterTypeInformation> ExtraParameters
=> ImmutableArray<Cci.IParameterTypeInformation>.Empty;
public Cci.IGenericMethodInstanceReference? AsGenericMethodInstanceReference => null;
public Cci.ISpecializedMethodReference? AsSpecializedMethodReference => null;
public Cci.CallingConvention CallingConvention => Cci.CallingConvention.HasThis;
public ushort ParameterCount => (ushort)_parameters.Length;
public ImmutableArray<Cci.ICustomModifier> RefCustomModifiers
=> ImmutableArray<Cci.ICustomModifier>.Empty;
public ImmutableArray<Cci.ICustomModifier> ReturnValueCustomModifiers
=> ImmutableArray<Cci.ICustomModifier>.Empty;
public Cci.ITypeReference GetContainingType(EmitContext context)
{
// We are not translating arrayType.
// It is an array type and it is never generic or contained in a generic.
return this.arrayType;
}
public IEnumerable<Cci.ICustomAttribute> GetAttributes(EmitContext context)
=> SpecializedCollections.EmptyEnumerable<Cci.ICustomAttribute>();
public void Dispatch(Cci.MetadataVisitor visitor)
=> visitor.Visit(this);
public Cci.IDefinition? AsDefinition(EmitContext context)
=> null;
public override string ToString()
=> ((object?)arrayType.GetInternalSymbol() ?? arrayType).ToString() + "." + Name;
Symbols.ISymbolInternal? Cci.IReference.GetInternalSymbol() => null;
public sealed override bool Equals(object? obj)
{
// It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used.
throw Roslyn.Utilities.ExceptionUtilities.Unreachable();
}
public sealed override int GetHashCode()
{
// It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used.
throw Roslyn.Utilities.ExceptionUtilities.Unreachable();
}
}
}
|