未验证 提交 7c5e8fdf 编写于 作者: N Neal Gafter 提交者: GitHub

Handle hoisted temporary variables of the switch expression pattern-matching machinery. (#37818)

Fixes #37713
上级 6a7de5a7
......@@ -62,7 +62,6 @@ public static OrderedSet<Symbol> Analyze(CSharpCompilation compilation, MethodSy
walker.CaptureVariable(method.ThisParameter, node.Syntax);
}
var variablesToHoist = walker._variablesToHoist;
var lazyDisallowedCaptures = walker._lazyDisallowedCaptures;
var allVariables = walker.variableBySlot;
......@@ -91,11 +90,10 @@ public static OrderedSet<Symbol> Analyze(CSharpCompilation compilation, MethodSy
}
}
var variablesToHoist = new OrderedSet<Symbol>();
if (compilation.Options.OptimizationLevel != OptimizationLevel.Release)
{
Debug.Assert(variablesToHoist.Count == 0);
// In debug build we hoist all locals and parameters:
// In debug build we hoist long-lived locals and parameters
foreach (var v in allVariables)
{
var symbol = v.Symbol;
......@@ -106,38 +104,25 @@ public static OrderedSet<Symbol> Analyze(CSharpCompilation compilation, MethodSy
}
}
// Hoist anything determined to be live across an await or yield
variablesToHoist.AddRange(walker._variablesToHoist);
return variablesToHoist;
}
private static bool HoistInDebugBuild(Symbol symbol)
{
// in Debug build hoist all parameters that can be hoisted:
if (symbol.Kind == SymbolKind.Parameter)
{
var parameter = (ParameterSymbol)symbol;
return !parameter.TypeWithAnnotations.IsRestrictedType();
}
if (symbol.Kind == SymbolKind.Local)
return (symbol) switch
{
LocalSymbol local = (LocalSymbol)symbol;
if (local.IsConst || local.IsPinned)
{
return false;
}
// hoist all user-defined locals that can be hoisted:
if (local.SynthesizedKind == SynthesizedLocalKind.UserDefined)
{
return !local.TypeWithAnnotations.IsRestrictedType();
}
// hoist all synthesized variables that have to survive state machine suspension:
return local.SynthesizedKind.MustSurviveStateMachineSuspension();
}
return false;
ParameterSymbol parameter =>
// in Debug build hoist all parameters that can be hoisted:
!parameter.Type.IsRestrictedType(),
LocalSymbol { IsConst: false, IsPinned: false } local =>
// hoist all user-defined locals and long-lived temps that can be hoisted:
local.SynthesizedKind.MustSurviveStateMachineSuspension() &&
!local.Type.IsRestrictedType(),
_ => false
};
}
private void MarkLocalsUnassigned()
......@@ -203,20 +188,9 @@ private void CaptureVariable(Symbol variable, SyntaxNode syntax)
var type = (variable.Kind == SymbolKind.Local) ? ((LocalSymbol)variable).Type : ((ParameterSymbol)variable).Type;
if (type.IsRestrictedType())
{
// error has already been reported.
if (variable is SynthesizedLocal local && local.SynthesizedKind != SynthesizedLocalKind.Spill)
{
return;
}
if (_lazyDisallowedCaptures == null)
{
_lazyDisallowedCaptures = new MultiDictionary<Symbol, SyntaxNode>();
}
_lazyDisallowedCaptures.Add(variable, syntax);
(_lazyDisallowedCaptures ??= new MultiDictionary<Symbol, SyntaxNode>()).Add(variable, syntax);
}
else if (compilation.Options.OptimizationLevel == OptimizationLevel.Release)
else
{
_variablesToHoist.Add(variable);
}
......
......@@ -273,8 +273,6 @@ private BoundStatement PossibleIteratorScope(ImmutableArray<LocalSymbol> locals,
continue;
}
Debug.Assert(local.SynthesizedKind.IsLongLived());
CapturedSymbolReplacement proxy;
bool reused = false;
if (!proxies.TryGetValue(local, out proxy))
......@@ -414,9 +412,6 @@ private bool MightContainReferences(TypeSymbol type)
private StateMachineFieldSymbol GetOrAllocateReusableHoistedField(TypeSymbol type, out bool reused, LocalSymbol local = null)
{
// In debug builds we don't reuse any hoisted variable.
Debug.Assert(F.Compilation.Options.OptimizationLevel == OptimizationLevel.Release);
ArrayBuilder<StateMachineFieldSymbol> fields;
if (_lazyAvailableReusableHoistedFields != null && _lazyAvailableReusableHoistedFields.TryGetValue(type, out fields) && fields.Count > 0)
{
......
......@@ -3596,5 +3596,118 @@ static async Task Main()
);
}
}
[Fact]
public void SpillStateMachineTemps()
{
var source = @"using System;
using System.Threading.Tasks;
public class C {
public static void Main()
{
Console.WriteLine(M1(new Q(), SF()).Result);
}
public static async Task<int> M1(object o, Task<bool> c)
{
return o switch
{
Q { F: { P1: true } } when await c => 1, // cached Q.F is alive
Q { F: { P2: true } } => 2,
_ => 3,
};
}
public static async Task<bool> SF()
{
await Task.Delay(10);
return false;
}
}
class Q
{
public F F => new F(true);
}
struct F
{
bool _result;
public F(bool result)
{
_result = result;
}
public bool P1 => _result;
public bool P2 => _result;
}
";
CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: "2");
CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: "2");
}
[Fact]
[WorkItem(37713, "https://github.com/dotnet/roslyn/issues/37713")]
public void RefStructInAsyncStateMachineWithWhenClause()
{
var source = @"
using System.Threading.Tasks;
class Program
{
async Task<int> M1(object o, Task<bool> c, int r)
{
return o switch
{
Q { F: { P1: true } } when await c => r, // error: cached Q.F is alive
Q { F: { P2: true } } => 2,
_ => 3,
};
}
async Task<int> M2(object o, Task<bool> c, int r)
{
return o switch
{
Q { F: { P1: true } } when await c => r, // ok: only Q.P1 is live
Q { F: { P1: true } } => 2,
_ => 3,
};
}
async Task<int> M3(object o, bool c, Task<int> r)
{
return o switch
{
Q { F: { P1: true } } when c => await r, // ok: nothing alive at await
Q { F: { P2: true } } => 2,
_ => 3,
};
}
async Task<int> M4(object o, Task<bool> c, int r)
{
return o switch
{
Q { F: { P1: true } } when await c => r, // ok: no switch state is alive
_ => 3,
};
}
}
public class Q
{
public S F => throw null!;
}
public ref struct S
{
public bool P1 => true;
public bool P2 => true;
}
";
CreateCompilation(source, options: TestOptions.DebugDll).VerifyDiagnostics().VerifyEmitDiagnostics(
// (9,20): error CS4013: Instance of type 'S' cannot be used inside a nested function, query expression, iterator block or async method
// Q { F: { P1: true } } when await c => r, // error: cached Q.F is alive
Diagnostic(ErrorCode.ERR_SpecialByRefInLambda, "{ P1: true }").WithArguments("S").WithLocation(9, 20)
);
CreateCompilation(source, options: TestOptions.ReleaseDll).VerifyDiagnostics().VerifyEmitDiagnostics(
// (9,20): error CS4013: Instance of type 'S' cannot be used inside a nested function, query expression, iterator block or async method
// Q { F: { P1: true } } when await c => r, // error: cached Q.F is alive
Diagnostic(ErrorCode.ERR_SpecialByRefInLambda, "{ P1: true }").WithArguments("S").WithLocation(9, 20)
);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册