StateMachineRewriter.cs 23.0 KB
Newer Older
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
P
Pilchie 已提交
2

3
using System;
P
Pilchie 已提交
4 5 6
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
7
using Microsoft.CodeAnalysis.CodeGen;
8
using Microsoft.CodeAnalysis.Collections;
P
Pilchie 已提交
9
using Microsoft.CodeAnalysis.CSharp.Symbols;
T
Tomas Matousek 已提交
10
using Microsoft.CodeAnalysis.PooledObjects;
P
Pilchie 已提交
11 12 13 14 15 16 17 18 19 20
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
    internal abstract class StateMachineRewriter
    {
        protected readonly BoundStatement body;
        protected readonly MethodSymbol method;
        protected readonly DiagnosticBag diagnostics;
        protected readonly SyntheticBoundNodeFactory F;
21 22
        protected readonly SynthesizedContainer stateMachineType;
        protected readonly VariableSlotAllocator slotAllocatorOpt;
23
        protected readonly SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals;
24

P
Pilchie 已提交
25
        protected FieldSymbol stateField;
26
        protected IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies;
27
        protected int nextFreeHoistedLocalSlot;
28
        protected IOrderedReadOnlySet<Symbol> hoistedVariables;
P
Pilchie 已提交
29
        protected Dictionary<Symbol, CapturedSymbolReplacement> initialParameters;
30
        protected FieldSymbol initialThreadIdField;
P
Pilchie 已提交
31 32 33 34

        protected StateMachineRewriter(
            BoundStatement body,
            MethodSymbol method,
35 36
            SynthesizedContainer stateMachineType,
            VariableSlotAllocator slotAllocatorOpt,
P
Pilchie 已提交
37
            TypeCompilationState compilationState,
38
            DiagnosticBag diagnostics)
P
Pilchie 已提交
39
        {
40 41
            Debug.Assert(body != null);
            Debug.Assert(method != null);
42
            Debug.Assert((object)stateMachineType != null);
43 44 45
            Debug.Assert(compilationState != null);
            Debug.Assert(diagnostics != null);

P
Pilchie 已提交
46 47
            this.body = body;
            this.method = method;
48 49
            this.stateMachineType = stateMachineType;
            this.slotAllocatorOpt = slotAllocatorOpt;
50
            this.synthesizedLocalOrdinals = new SynthesizedLocalOrdinalsDispenser();
P
Pilchie 已提交
51
            this.diagnostics = diagnostics;
52

P
Pilchie 已提交
53
            this.F = new SyntheticBoundNodeFactory(method, body.Syntax, compilationState, diagnostics);
54
            Debug.Assert(TypeSymbol.Equals(F.CurrentType, method.ContainingType, TypeCompareKind.ConsiderEverything2));
P
Pilchie 已提交
55 56 57 58
            Debug.Assert(F.Syntax == body.Syntax);
        }

        /// <summary>
59
        /// True if the initial values of locals in the rewritten method and the initial thread ID need to be preserved. (e.g. enumerable iterator methods and async-enumerable iterator methods)
P
Pilchie 已提交
60
        /// </summary>
61
        protected abstract bool PreserveInitialParameterValuesAndThreadId { get; }
P
Pilchie 已提交
62 63

        /// <summary>
64
        /// Add fields to the state machine class that control the state machine.
P
Pilchie 已提交
65
        /// </summary>
66
        protected abstract void GenerateControlFields();
P
Pilchie 已提交
67 68 69 70

        /// <summary>
        /// Initialize the state machine class.
        /// </summary>
71
        protected abstract void InitializeStateMachine(ArrayBuilder<BoundStatement> bodyBuilder, NamedTypeSymbol frameType, LocalSymbol stateMachineLocal);
P
Pilchie 已提交
72 73

        /// <summary>
74
        /// Generate implementation-specific state machine initialization for the kickoff method body.
P
Pilchie 已提交
75
        /// </summary>
76
        protected abstract BoundStatement GenerateStateMachineCreation(LocalSymbol stateMachineVariable, NamedTypeSymbol frameType);
P
Pilchie 已提交
77 78 79 80 81 82 83 84 85 86 87 88 89

        /// <summary>
        /// Generate implementation-specific state machine member method implementations.
        /// </summary>
        protected abstract void GenerateMethodImplementations();

        protected BoundStatement Rewrite()
        {
            if (this.body.HasErrors)
            {
                return this.body;
            }

90
            F.OpenNestedType(stateMachineType);
P
Pilchie 已提交
91

92
            GenerateControlFields();
P
Pilchie 已提交
93

94 95 96 97 98 99 100
            if (PreserveInitialParameterValuesAndThreadId && CanGetThreadId())
            {
                // if it is an enumerable or async-enumerable, and either Environment.CurrentManagedThreadId or Thread.ManagedThreadId are available
                // add a field: int initialThreadId
                initialThreadIdField = F.StateMachineField(F.SpecialType(SpecialType.System_Int32), GeneratedNames.MakeIteratorCurrentThreadIdFieldName());
            }

101
            // fields for the initial values of all the parameters of the method
102
            if (PreserveInitialParameterValuesAndThreadId)
P
Pilchie 已提交
103 104 105 106
            {
                initialParameters = new Dictionary<Symbol, CapturedSymbolReplacement>();
            }

107
            // fields for the captured variables of the method
108 109
            var variablesToHoist = IteratorAndAsyncCaptureWalker.Analyze(F.Compilation, method, body, diagnostics);

110 111 112 113 114 115
            if (diagnostics.HasAnyErrors())
            {
                // Avoid triggering assertions in further lowering.
                return new BoundBadStatement(F.Syntax, ImmutableArray<BoundNode>.Empty, hasErrors: true);
            }

116 117 118
            CreateNonReusableLocalProxies(variablesToHoist, out this.nonReusableLocalProxies, out this.nextFreeHoistedLocalSlot);

            this.hoistedVariables = variablesToHoist;
119

P
Pilchie 已提交
120 121
            GenerateMethodImplementations();

122 123
            // Return a replacement body for the kickoff method
            return GenerateKickoffMethodBody();
P
Pilchie 已提交
124 125
        }

126 127 128 129
        private void CreateNonReusableLocalProxies(
            IEnumerable<Symbol> variablesToHoist,
            out IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> proxies,
            out int nextFreeHoistedLocalSlot)
P
Pilchie 已提交
130
        {
131
            var proxiesBuilder = new Dictionary<Symbol, CapturedSymbolReplacement>();
132

133
            var typeMap = stateMachineType.TypeMap;
134 135
            bool isDebugBuild = F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug;
            bool mapToPreviousFields = isDebugBuild && slotAllocatorOpt != null;
136

137
            nextFreeHoistedLocalSlot = mapToPreviousFields ? slotAllocatorOpt.PreviousHoistedLocalSlotCount : 0;
138

139
            foreach (var variable in variablesToHoist)
P
Pilchie 已提交
140
            {
141 142 143
                Debug.Assert(variable.Kind == SymbolKind.Local || variable.Kind == SymbolKind.Parameter);

                if (variable.Kind == SymbolKind.Local)
P
Pilchie 已提交
144
                {
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
                    var local = (LocalSymbol)variable;
                    var synthesizedKind = local.SynthesizedKind;

                    if (!synthesizedKind.MustSurviveStateMachineSuspension())
                    {
                        continue;
                    }

                    // no need to hoist constants
                    if (local.IsConst)
                    {
                        continue;
                    }

                    if (local.RefKind != RefKind.None)
                    {
                        // we'll create proxies for these variables later:
162
                        Debug.Assert(synthesizedKind == SynthesizedLocalKind.Spill);
163 164 165 166 167 168
                        continue;
                    }

                    Debug.Assert(local.RefKind == RefKind.None);
                    StateMachineFieldSymbol field = null;

V
vsadov 已提交
169
                    if (ShouldPreallocateNonReusableProxy(local))
170 171
                    {
                        // variable needs to be hoisted
172
                        var fieldType = typeMap.SubstituteType(local.Type).Type;
173 174 175 176 177 178

                        LocalDebugId id;
                        int slotIndex = -1;

                        if (isDebugBuild)
                        {
179
                            // Calculate local debug id.
180 181 182 183
                            //
                            // EnC: When emitting the baseline (gen 0) the id is stored in a custom debug information attached to the kickoff method.
                            //      When emitting a delta the id is only used to map to the existing field in the previous generation.
                            SyntaxNode declaratorSyntax = local.GetDeclaratorSyntax();
184
                            int syntaxOffset = method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(declaratorSyntax), declaratorSyntax.SyntaxTree);
185 186 187
                            int ordinal = synthesizedLocalOrdinals.AssignLocalOrdinal(synthesizedKind, syntaxOffset);
                            id = new LocalDebugId(syntaxOffset, ordinal);

188 189
                            // map local id to the previous id, if available:
                            int previousSlotIndex;
190
                            if (mapToPreviousFields && slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(
V
vsadov 已提交
191 192 193 194
                                declaratorSyntax,
                                F.ModuleBuilderOpt.Translate(fieldType, declaratorSyntax, diagnostics),
                                synthesizedKind,
                                id,
195 196
                                diagnostics,
                                out previousSlotIndex))
197
                            {
198
                                slotIndex = previousSlotIndex;
199 200 201 202 203 204 205
                            }
                        }
                        else
                        {
                            id = LocalDebugId.None;
                        }

206
                        if (slotIndex == -1)
207 208 209
                        {
                            slotIndex = nextFreeHoistedLocalSlot++;
                        }
210

211
                        string fieldName = GeneratedNames.MakeHoistedLocalFieldName(synthesizedKind, slotIndex, local.Name);
212
                        field = F.StateMachineField(fieldType, fieldName, new LocalSlotDebugInfo(synthesizedKind, id), slotIndex);
213 214 215
                    }

                    if (field != null)
216
                    {
217
                        proxiesBuilder.Add(local, new CapturedToStateMachineFieldReplacement(field, isReusable: false));
218
                    }
P
Pilchie 已提交
219
                }
220
                else
P
Pilchie 已提交
221
                {
222
                    var parameter = (ParameterSymbol)variable;
P
Pilchie 已提交
223 224
                    if (parameter.IsThis)
                    {
225
                        var containingType = method.ContainingType;
226
                        var proxyField = F.StateMachineField(containingType, GeneratedNames.ThisProxyFieldName(), isPublic: true, isThis: true);
227
                        proxiesBuilder.Add(parameter, new CapturedToStateMachineFieldReplacement(proxyField, isReusable: false));
P
Pilchie 已提交
228

229
                        if (PreserveInitialParameterValuesAndThreadId)
P
Pilchie 已提交
230
                        {
231
                            var initialThis = containingType.IsStructType() ?
232
                                F.StateMachineField(containingType, GeneratedNames.StateMachineThisParameterProxyName(), isPublic: true, isThis: true) : proxyField;
233

234
                            initialParameters.Add(parameter, new CapturedToStateMachineFieldReplacement(initialThis, isReusable: false));
P
Pilchie 已提交
235 236 237 238
                        }
                    }
                    else
                    {
239
                        // The field needs to be public iff it is initialized directly from the kickoff method
240
                        // (i.e. not for IEnumerable which loads the values from parameter proxies).
241
                        var proxyField = F.StateMachineField(typeMap.SubstituteType(parameter.Type).Type, parameter.Name, isPublic: !PreserveInitialParameterValuesAndThreadId);
242
                        proxiesBuilder.Add(parameter, new CapturedToStateMachineFieldReplacement(proxyField, isReusable: false));
P
Pilchie 已提交
243

244
                        if (PreserveInitialParameterValuesAndThreadId)
P
Pilchie 已提交
245
                        {
246
                            var field = F.StateMachineField(typeMap.SubstituteType(parameter.Type).Type, GeneratedNames.StateMachineParameterProxyFieldName(parameter.Name), isPublic: true);
247
                            initialParameters.Add(parameter, new CapturedToStateMachineFieldReplacement(field, isReusable: false));
P
Pilchie 已提交
248 249 250 251
                        }
                    }
                }
            }
252

253
            proxies = proxiesBuilder;
P
Pilchie 已提交
254 255
        }

V
vsadov 已提交
256 257 258 259 260
        private bool ShouldPreallocateNonReusableProxy(LocalSymbol local)
        {
            var synthesizedKind = local.SynthesizedKind;
            var optimizationLevel = F.Compilation.Options.OptimizationLevel;

C
Charles Stoner 已提交
261
            // do not preallocate proxy fields for user defined locals in release
V
vsadov 已提交
262 263 264 265 266 267 268 269 270 271
            // otherwise we will be allocating fields for all locals even when fields can be reused
            // see https://github.com/dotnet/roslyn/issues/15290
            if (optimizationLevel == OptimizationLevel.Release && synthesizedKind == SynthesizedLocalKind.UserDefined)
            {
                return false;
            }

            return !synthesizedKind.IsSlotReusable(optimizationLevel);
        }

272
        private BoundStatement GenerateKickoffMethodBody()
P
Pilchie 已提交
273
        {
274
            F.CurrentFunction = method;
P
Pilchie 已提交
275
            var bodyBuilder = ArrayBuilder<BoundStatement>.GetInstance();
276

277
            var frameType = method.IsGenericMethod ? stateMachineType.Construct(method.TypeArgumentsWithAnnotations, unbound: false) : stateMachineType;
P
Pilchie 已提交
278 279 280 281
            LocalSymbol stateMachineVariable = F.SynthesizedLocal(frameType, null);
            InitializeStateMachine(bodyBuilder, frameType, stateMachineVariable);

            // plus code to initialize all of the parameter proxies result.proxy
282
            var proxies = PreserveInitialParameterValuesAndThreadId ? initialParameters : nonReusableLocalProxies;
P
Pilchie 已提交
283 284 285 286 287 288 289

            // starting with the "this" proxy
            if (!method.IsStatic)
            {
                Debug.Assert((object)method.ThisParameter != null);

                CapturedSymbolReplacement proxy;
290 291
                if (proxies.TryGetValue(method.ThisParameter, out proxy))
                {
P
Pilchie 已提交
292
                    bodyBuilder.Add(F.Assignment(proxy.Replacement(F.Syntax, frameType1 => F.Local(stateMachineVariable)), F.This()));
293
                }
P
Pilchie 已提交
294 295 296 297 298
            }

            foreach (var parameter in method.Parameters)
            {
                CapturedSymbolReplacement proxy;
299 300
                if (proxies.TryGetValue(parameter, out proxy))
                {
P
Pilchie 已提交
301 302
                    bodyBuilder.Add(F.Assignment(proxy.Replacement(F.Syntax, frameType1 => F.Local(stateMachineVariable)),
                                                 F.Parameter(parameter)));
303
                }
P
Pilchie 已提交
304 305
            }

306
            bodyBuilder.Add(GenerateStateMachineCreation(stateMachineVariable, frameType));
P
Pilchie 已提交
307
            return F.Block(
308
                ImmutableArray.Create(stateMachineVariable),
P
Pilchie 已提交
309 310 311
                bodyBuilder.ToImmutableAndFree());
        }

312 313 314
        protected SynthesizedImplementationMethod OpenMethodImplementation(
            MethodSymbol methodToImplement,
            string methodName = null,
315
            bool hasMethodBodyDependency = false)
316
        {
317
            var result = new SynthesizedStateMachineDebuggerHiddenMethod(methodName, methodToImplement, (StateMachineTypeSymbol)F.CurrentType, null, hasMethodBodyDependency);
318
            F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, result);
319
            F.CurrentFunction = result;
320 321 322
            return result;
        }

323
        protected MethodSymbol OpenPropertyImplementation(MethodSymbol getterToImplement)
324
        {
325
            var prop = new SynthesizedStateMachineProperty(getterToImplement, (StateMachineTypeSymbol)F.CurrentType);
326
            F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, prop);
327 328

            var getter = prop.GetMethod;
329
            F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, getter);
330

331
            F.CurrentFunction = getter;
332 333
            return getter;
        }
334

335
        protected SynthesizedImplementationMethod OpenMoveNextMethodImplementation(MethodSymbol methodToImplement)
336
        {
337 338
            var result = new SynthesizedStateMachineMoveNextMethod(methodToImplement, (StateMachineTypeSymbol)F.CurrentType);
            F.ModuleBuilderOpt.AddSynthesizedDefinition(F.CurrentType, result);
339
            F.CurrentFunction = result;
340
            return result;
341
        }
342 343 344 345 346 347 348

        /// <summary>
        /// Produce Environment.CurrentManagedThreadId if available, otherwise CurrentThread.ManagedThreadId
        /// </summary>
        protected BoundExpression MakeCurrentThreadId()
        {
            Debug.Assert(CanGetThreadId());
349 350 351 352

            // .NET Core has removed the Thread class. We can get the managed thread id by making a call to
            // Environment.CurrentManagedThreadId. If that method is not present (pre 4.5) fall back to the old behavior.

353 354 355 356 357 358 359 360 361 362 363 364 365
            var currentManagedThreadIdProperty = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Environment__CurrentManagedThreadId, isOptional: true);
            if ((object)currentManagedThreadIdProperty != null)
            {
                MethodSymbol currentManagedThreadIdMethod = currentManagedThreadIdProperty.GetMethod;
                if ((object)currentManagedThreadIdMethod != null)
                {
                    return F.Call(null, currentManagedThreadIdMethod);
                }
            }

            return F.Property(F.Property(WellKnownMember.System_Threading_Thread__CurrentThread), WellKnownMember.System_Threading_Thread__ManagedThreadId);
        }

366 367 368 369 370 371 372 373 374 375
        /// <summary>
        /// Generate the GetEnumerator() method for iterators and GetAsyncEnumerator() for async-iterators.
        /// </summary>
        protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSymbol getEnumeratorMethod, ref BoundExpression managedThreadId, int initialState)
        {
            // Produces:
            //    {StateMachineType} result;
            //    if (this.initialThreadId == {managedThreadId} && this.state == -2)
            //    {
            //        this.state = {initialState};
376
            //        extraReset
377 378 379 380 381 382
            //        result = this;
            //    }
            //    else
            //    {
            //        result = new {StateMachineType}({initialState});
            //    }
383
            //
384
            //    result.parameter = this.parameterProxy; // OR more complex initialization for async-iterator parameter marked with [EnumeratorCancellation]
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402

            // The implementation doesn't depend on the method body of the iterator method.
            // Only on its parameters and staticness.
            var getEnumerator = OpenMethodImplementation(
                getEnumeratorMethod,
                hasMethodBodyDependency: false);

            var bodyBuilder = ArrayBuilder<BoundStatement>.GetInstance();
            // {StateMachineType} result;
            var resultVariable = F.SynthesizedLocal(stateMachineType, null);
            // result = new {StateMachineType}({initialState})
            BoundStatement makeIterator = F.Assignment(F.Local(resultVariable), F.New(stateMachineType.Constructor, F.Literal(initialState)));

            var thisInitialized = F.GenerateLabel("thisInitialized");

            if ((object)initialThreadIdField != null)
            {
                managedThreadId = MakeCurrentThreadId();
403 404

                var thenBuilder = ArrayBuilder<BoundStatement>.GetInstance(4);
405
                GenerateResetInstance(thenBuilder, initialState);
406 407 408 409 410

                thenBuilder.Add(
                    // result = this;
                    F.Assignment(F.Local(resultVariable), F.This()));

411
                if (method.IsStatic || method.ThisParameter.Type.IsReferenceType)
412 413 414 415 416 417
                {
                    // if this is a reference type, no need to copy it since it is not assignable
                    thenBuilder.Add(
                        // goto thisInitialized;
                        F.Goto(thisInitialized));
                }
418

419 420 421
                makeIterator = F.If(
                    // if (this.state == -2 && this.initialThreadId == Thread.CurrentThread.ManagedThreadId)
                    condition: F.LogicalAnd(
422
                        F.IntEqual(F.Field(F.This(), stateField), F.Literal(StateMachineStates.FinishedStateMachine)),
423
                        F.IntEqual(F.Field(F.This(), initialThreadIdField), managedThreadId)),
424
                    thenClause: F.Block(thenBuilder.ToImmutableAndFree()),
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
                    elseClauseOpt: makeIterator);
            }

            bodyBuilder.Add(makeIterator);

            // Initialize all the parameter copies
            var copySrc = initialParameters;
            var copyDest = nonReusableLocalProxies;
            if (!method.IsStatic)
            {
                // starting with "this"
                CapturedSymbolReplacement proxy;
                if (copyDest.TryGetValue(method.ThisParameter, out proxy))
                {
                    bodyBuilder.Add(
                        F.Assignment(
                            proxy.Replacement(F.Syntax, stateMachineType => F.Local(resultVariable)),
                            copySrc[method.ThisParameter].Replacement(F.Syntax, stateMachineType => F.This())));
                }
            }

            bodyBuilder.Add(F.Label(thisInitialized));

            foreach (var parameter in method.Parameters)
            {
                CapturedSymbolReplacement proxy;
                if (copyDest.TryGetValue(parameter, out proxy))
                {
453 454 455 456 457
                    // result.parameter
                    BoundExpression resultParameter = proxy.Replacement(F.Syntax, stateMachineType => F.Local(resultVariable));
                    // this.parameterProxy
                    BoundExpression parameterProxy = copySrc[parameter].Replacement(F.Syntax, stateMachineType => F.This());
                    BoundStatement copy = InitializeParameterField(getEnumeratorMethod, parameter, resultParameter, parameterProxy);
458 459

                    bodyBuilder.Add(copy);
460 461 462 463 464 465 466 467
                }
            }

            bodyBuilder.Add(F.Return(F.Local(resultVariable)));
            F.CloseMethod(F.Block(ImmutableArray.Create(resultVariable), bodyBuilder.ToImmutableAndFree()));
            return getEnumerator;
        }

468 469 470 471 472 473 474 475 476 477
        /// <summary>
        /// Generate logic to reset the current instance (rather than creating a new instance)
        /// </summary>
        protected virtual void GenerateResetInstance(ArrayBuilder<BoundStatement> builder, int initialState)
        {
            builder.Add(
                // this.state = {initialState};
                F.Assignment(F.Field(F.This(), stateField), F.Literal(initialState)));
        }

478 479 480 481 482 483 484 485
        protected virtual BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy)
        {
            Debug.Assert(!method.IsIterator || !method.IsAsync); // an override handles async-iterators

            // result.parameter = this.parameterProxy;
            return F.Assignment(resultParameter, parameterProxy);
        }

486 487 488 489 490 491 492 493
        /// <summary>
        /// Returns true if either Thread.ManagedThreadId or Environment.CurrentManagedThreadId are available
        /// </summary>
        protected bool CanGetThreadId()
        {
            return (object)F.WellKnownMember(WellKnownMember.System_Threading_Thread__ManagedThreadId, isOptional: true) != null ||
                (object)F.WellKnownMember(WellKnownMember.System_Environment__CurrentManagedThreadId, isOptional: true) != null;
        }
P
Pilchie 已提交
494
    }
495
}