Skip to content

Commit

Permalink
Port almost latest linker changes
Browse files Browse the repository at this point in the history
This was needed to fix failures in libraries build with AOT. The underlying problem is not fully fixed: https://github.com/dotnet/linker/issues/2874
But with these changes it doesn't crash the compiler, it just leads to sometimes imprecise warnings.
  • Loading branch information
vitek-karas committed Jul 6, 2022
1 parent 10bbc6d commit 53800b7
Show file tree
Hide file tree
Showing 34 changed files with 1,235 additions and 221 deletions.
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
TODO: Remove pinned version once arcade supplies a compiler that enables the repo to compile.
-->
<MicrosoftNetCompilersToolsetVersion>4.4.0-1.22315.13</MicrosoftNetCompilersToolsetVersion>
<StaticCsVersion>0.1.0</StaticCsVersion>
<!-- SDK dependencies -->
<MicrosoftDotNetCompatibilityVersion>2.0.0-preview.4.22252.4</MicrosoftDotNetCompatibilityVersion>
<!-- Arcade dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,14 @@ static IEnumerable<MetadataType> GetCompilerGeneratedNestedTypes(TypeDesc type)
}
}

public static bool IsHoistedLocal(FieldDesc field)
{
// Treat all fields on compiler-generated types as hoisted locals.
// This avoids depending on the name mangling scheme for hoisted locals.
var declaringTypeName = field.OwningType.Name;
return CompilerGeneratedNames.IsLambdaDisplayClass(declaringTypeName) || CompilerGeneratedNames.IsStateMachineType(declaringTypeName);
}

// "Nested function" refers to lambdas and local functions.
public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace ILLink.Shared.TrimAnalysis
/// <summary>
/// Caches dataflow annotations for type members.
/// </summary>
public partial class FlowAnnotations
sealed public partial class FlowAnnotations
{
private readonly TypeAnnotationsHashtable _hashtable;
private readonly Logger _logger;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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.Diagnostics;
using Internal.TypeSystem;

#nullable enable

namespace ILCompiler.Dataflow
{
// This represents a field which has been generated by the compiler as the
// storage location for a hoisted local (a local variable which is lifted to a
// field on a state machine type, or to a field on a closure accessed by lambdas
// or local functions).
public readonly struct HoistedLocalKey : IEquatable<HoistedLocalKey>
{
readonly FieldDesc Field;

public HoistedLocalKey(FieldDesc field)
{
Debug.Assert(CompilerGeneratedState.IsHoistedLocal(field));
Field = field;
}

public bool Equals(HoistedLocalKey other) => Field.Equals(other.Field);

public override bool Equals(object? obj) => obj is HoistedLocalKey other && Equals(other);

public override int GetHashCode() => Field.GetHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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.Diagnostics.CodeAnalysis;

using ILLink.Shared.DataFlow;
using Internal.IL;
using Internal.TypeSystem;

using HoistedLocalState = ILLink.Shared.DataFlow.DefaultValueDictionary<
ILCompiler.Dataflow.HoistedLocalKey,
ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>>;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;

#nullable enable

namespace ILCompiler.Dataflow
{
// Wrapper that implements IEquatable for MethodBody.
readonly record struct MethodBodyValue(MethodIL MethodBody) : IEquatable<MethodBodyValue>
{
bool IEquatable<MethodBodyValue>.Equals(ILCompiler.Dataflow.MethodBodyValue other)
=> other.MethodBody.OwningMethod == MethodBody.OwningMethod;

public override int GetHashCode() => MethodBody.OwningMethod.GetHashCode();
}

// Tracks the set of methods which get analyzer together during interprocedural analysis,
// and the possible states of hoisted locals in state machine methods and lambdas/local functions.
struct InterproceduralState : IEquatable<InterproceduralState>
{
readonly ILProvider _ilProvider;
public ValueSet<MethodBodyValue> MethodBodies;
public HoistedLocalState HoistedLocals;
readonly InterproceduralStateLattice lattice;

public InterproceduralState(ILProvider ilProvider, ValueSet<MethodBodyValue> methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice)
=> (_ilProvider, MethodBodies, HoistedLocals, this.lattice) = (ilProvider, methodBodies, hoistedLocals, lattice);

public bool Equals(InterproceduralState other)
=> MethodBodies.Equals(other.MethodBodies) && HoistedLocals.Equals(other.HoistedLocals);

public InterproceduralState Clone()
=> new(_ilProvider, MethodBodies.Clone(), HoistedLocals.Clone(), lattice);

public void TrackMethod(MethodDesc method)
{
if (!TryGetMethodBody(method, out MethodIL? methodBody))
return;

TrackMethod(methodBody);
}

public void TrackMethod(MethodIL methodBody)
{
Debug.Assert(methodBody.GetMethodILDefinition() == methodBody);
methodBody = GetInstantiatedMethodIL(methodBody);

// Work around the fact that ValueSet is readonly
var methodsList = new List<MethodBodyValue>(MethodBodies);
methodsList.Add(new MethodBodyValue(methodBody));

// For state machine methods, also scan the state machine members.
// Simplification: assume that all generated methods of the state machine type are
// reached at the point where the state machine method is reached.
if (CompilerGeneratedState.TryGetStateMachineType(methodBody.OwningMethod, out MetadataType? stateMachineType))
{
foreach (var stateMachineMethod in stateMachineType.GetMethods())
{
Debug.Assert(!CompilerGeneratedNames.IsLambdaOrLocalFunction(stateMachineMethod.Name));
if (TryGetMethodBody(stateMachineMethod, out MethodIL? stateMachineMethodBody))
{
stateMachineMethodBody = GetInstantiatedMethodIL(stateMachineMethodBody);
methodsList.Add(new MethodBodyValue(stateMachineMethodBody));
}
}
}

MethodBodies = new ValueSet<MethodBodyValue>(methodsList);

static MethodIL GetInstantiatedMethodIL(MethodIL methodIL)
{
if (methodIL.OwningMethod.HasInstantiation || methodIL.OwningMethod.OwningType.HasInstantiation)
{
// We instantiate the body over the generic parameters.
//
// This will transform references like "call Foo<!0>.Method(!0 arg)" into
// "call Foo<T>.Method(T arg)". We do this to avoid getting confused about what
// context the generic variables refer to - in the above example, we would see
// two !0's - one refers to the generic parameter of the type that owns the method with
// the call, but the other one (in the signature of "Method") actually refers to
// the generic parameter of Foo.
//
// If we don't do this translation, retrieving the signature of the called method
// would attempt to do bogus substitutions.
//
// By doing the following transformation, we ensure we don't see the generic variables
// that need to be bound to the context of the currently analyzed method.
methodIL = new InstantiatedMethodIL(methodIL.OwningMethod, methodIL);
}

return methodIL;
}
}

public void SetHoistedLocal(HoistedLocalKey key, MultiValue value)
{
// 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.HoistedLocalsLattice.ValueLattice.Meet(
HoistedLocals.Get(key), value));
}

public MultiValue GetHoistedLocal(HoistedLocalKey key)
=> HoistedLocals.Get(key);

bool TryGetMethodBody(MethodDesc method, [NotNullWhen(true)] out MethodIL? methodBody)
{
methodBody = null;

if (method.IsPInvoke)
return false;

MethodIL methodIL = _ilProvider.GetMethodIL(method);
if (methodIL == null)
return false;

methodBody = methodIL;
return true;
}
}

struct InterproceduralStateLattice : ILattice<InterproceduralState>
{
private readonly ILProvider _ilProvider;
public readonly ValueSetLattice<MethodBodyValue> MethodBodyLattice;
public readonly DictionaryLattice<HoistedLocalKey, MultiValue, ValueSetLattice<SingleValue>> HoistedLocalsLattice;

public InterproceduralStateLattice(
ILProvider ilProvider,
ValueSetLattice<MethodBodyValue> methodBodyLattice,
DictionaryLattice<HoistedLocalKey, MultiValue, ValueSetLattice<SingleValue>> hoistedLocalsLattice)
=> (_ilProvider, MethodBodyLattice, HoistedLocalsLattice) = (ilProvider, methodBodyLattice, hoistedLocalsLattice);

public InterproceduralState Top => new InterproceduralState(_ilProvider, MethodBodyLattice.Top, HoistedLocalsLattice.Top, this);

public InterproceduralState Meet(InterproceduralState left, InterproceduralState right)
=> new(
_ilProvider,
MethodBodyLattice.Meet(left.MethodBodies, right.MethodBodies),
HoistedLocalsLattice.Meet(left.HoistedLocals, right.HoistedLocals),
this);
}
}
Loading

0 comments on commit 53800b7

Please sign in to comment.