-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use a simple temp instead of InlineArray1 #73086
Changes from 1 commit
5ee9248
3e51b81
60d90aa
9d69fa7
754c7c4
e9e7d2a
410ec68
35c2769
8712cd2
1c459b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20919,7 +20919,7 @@ .maxstack 5 | |
} | ||
|
||
[Fact] | ||
public void Span_SingleElement_TempReuse() | ||
public void Span_SingleElement_TempsAreNotReused() | ||
{ | ||
var source = """ | ||
using System; | ||
|
@@ -20933,6 +20933,7 @@ static void M(int x) | |
{ | ||
Span<int> y = [x]; | ||
Console.Write(y[0]); | ||
y[0]++; | ||
} | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test where we create single-element collection twice in the same block (not different blocks like in this test)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed this is the type of test that I was most worried about. Re-using the temp between two different blocks is just fine. Re-using it within the same block would be a significant codegen error. Want to make sure that we're not doing that. |
||
Span<int> y = [x]; | ||
|
@@ -20942,13 +20943,13 @@ static void M(int x) | |
} | ||
"""; | ||
|
||
var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); | ||
var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); | ||
verifier.VerifyDiagnostics(); | ||
|
||
verifier.VerifyIL("Program.M", """ | ||
{ | ||
// Code size 49 (0x31) | ||
.maxstack 2 | ||
// Code size 62 (0x3e) | ||
.maxstack 3 | ||
.locals init (int V_0, | ||
int V_1, | ||
System.Span<int> V_2, //y | ||
|
@@ -20963,26 +20964,34 @@ .maxstack 2 | |
IL_000d: call "ref int System.Span<int>.this[int].get" | ||
IL_0012: ldind.i4 | ||
IL_0013: call "void System.Console.Write(int)" | ||
IL_0018: ldarg.0 | ||
IL_0019: stloc.1 | ||
IL_001a: ldloca.s V_1 | ||
IL_001c: newobj "System.Span<int>..ctor(ref int)" | ||
IL_0021: stloc.3 | ||
IL_0022: ldloca.s V_3 | ||
IL_0024: ldc.i4.0 | ||
IL_0025: call "ref int System.Span<int>.this[int].get" | ||
IL_002a: ldind.i4 | ||
IL_002b: call "void System.Console.Write(int)" | ||
IL_0030: ret | ||
IL_0018: ldloca.s V_2 | ||
IL_001a: ldc.i4.0 | ||
IL_001b: call "ref int System.Span<int>.this[int].get" | ||
IL_0020: dup | ||
IL_0021: ldind.i4 | ||
IL_0022: ldc.i4.1 | ||
IL_0023: add | ||
IL_0024: stind.i4 | ||
IL_0025: ldarg.0 | ||
IL_0026: stloc.1 | ||
IL_0027: ldloca.s V_1 | ||
IL_0029: newobj "System.Span<int>..ctor(ref int)" | ||
IL_002e: stloc.3 | ||
IL_002f: ldloca.s V_3 | ||
IL_0031: ldc.i4.0 | ||
IL_0032: call "ref int System.Span<int>.this[int].get" | ||
IL_0037: ldind.i4 | ||
IL_0038: call "void System.Console.Write(int)" | ||
IL_003d: ret | ||
} | ||
"""); | ||
|
||
verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); | ||
verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); | ||
verifier.VerifyDiagnostics(); | ||
|
||
verifier.VerifyIL("Program.M", """ | ||
{ | ||
// Code size 63 (0x3f) | ||
// Code size 76 (0x4c) | ||
.maxstack 5 | ||
.locals init (System.Span<int> V_0, //y | ||
System.Span<int> V_1) //y | ||
|
@@ -20999,20 +21008,141 @@ .maxstack 5 | |
IL_0014: call "ref int System.Span<int>.this[int].get" | ||
IL_0019: ldind.i4 | ||
IL_001a: call "void System.Console.Write(int)" | ||
IL_001f: ldloca.s V_1 | ||
IL_0021: ldc.i4.1 | ||
IL_0022: newarr "int" | ||
IL_001f: ldloca.s V_0 | ||
IL_0021: ldc.i4.0 | ||
IL_0022: call "ref int System.Span<int>.this[int].get" | ||
IL_0027: dup | ||
IL_0028: ldc.i4.0 | ||
IL_0029: ldarg.0 | ||
IL_002a: stelem.i4 | ||
IL_002b: call "System.Span<int>..ctor(int[])" | ||
IL_0030: ldloca.s V_1 | ||
IL_0032: ldc.i4.0 | ||
IL_0033: call "ref int System.Span<int>.this[int].get" | ||
IL_0038: ldind.i4 | ||
IL_0039: call "void System.Console.Write(int)" | ||
IL_003e: ret | ||
IL_0028: ldind.i4 | ||
IL_0029: ldc.i4.1 | ||
IL_002a: add | ||
IL_002b: stind.i4 | ||
IL_002c: ldloca.s V_1 | ||
IL_002e: ldc.i4.1 | ||
IL_002f: newarr "int" | ||
IL_0034: dup | ||
IL_0035: ldc.i4.0 | ||
IL_0036: ldarg.0 | ||
IL_0037: stelem.i4 | ||
IL_0038: call "System.Span<int>..ctor(int[])" | ||
IL_003d: ldloca.s V_1 | ||
IL_003f: ldc.i4.0 | ||
IL_0040: call "ref int System.Span<int>.this[int].get" | ||
IL_0045: ldind.i4 | ||
IL_0046: call "void System.Console.Write(int)" | ||
IL_004b: ret | ||
} | ||
"""); | ||
} | ||
|
||
[Fact] | ||
public void Span_SingleElement_TempsAreNotReused_SameBlock() | ||
{ | ||
var source = """ | ||
using System; | ||
|
||
class Program | ||
{ | ||
static void Main() => M(1); | ||
|
||
static void M(int x) | ||
{ | ||
Span<int> y = [x]; | ||
Console.Write(y[0]); | ||
y[0]++; | ||
|
||
Span<int> z = [x]; | ||
Console.Write(z[0]); | ||
} | ||
} | ||
"""; | ||
|
||
var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); | ||
verifier.VerifyDiagnostics(); | ||
|
||
verifier.VerifyIL("Program.M", """ | ||
{ | ||
// Code size 62 (0x3e) | ||
.maxstack 3 | ||
.locals init (System.Span<int> V_0, //y | ||
System.Span<int> V_1, //z | ||
int V_2, | ||
int V_3) | ||
IL_0000: ldarg.0 | ||
IL_0001: stloc.2 | ||
IL_0002: ldloca.s V_2 | ||
IL_0004: newobj "System.Span<int>..ctor(ref int)" | ||
IL_0009: stloc.0 | ||
IL_000a: ldloca.s V_0 | ||
IL_000c: ldc.i4.0 | ||
IL_000d: call "ref int System.Span<int>.this[int].get" | ||
IL_0012: ldind.i4 | ||
IL_0013: call "void System.Console.Write(int)" | ||
IL_0018: ldloca.s V_0 | ||
IL_001a: ldc.i4.0 | ||
IL_001b: call "ref int System.Span<int>.this[int].get" | ||
IL_0020: dup | ||
IL_0021: ldind.i4 | ||
IL_0022: ldc.i4.1 | ||
IL_0023: add | ||
IL_0024: stind.i4 | ||
IL_0025: ldarg.0 | ||
IL_0026: stloc.3 | ||
IL_0027: ldloca.s V_3 | ||
IL_0029: newobj "System.Span<int>..ctor(ref int)" | ||
IL_002e: stloc.1 | ||
IL_002f: ldloca.s V_1 | ||
IL_0031: ldc.i4.0 | ||
IL_0032: call "ref int System.Span<int>.this[int].get" | ||
IL_0037: ldind.i4 | ||
IL_0038: call "void System.Console.Write(int)" | ||
IL_003d: ret | ||
} | ||
"""); | ||
|
||
verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("11")); | ||
verifier.VerifyDiagnostics(); | ||
|
||
verifier.VerifyIL("Program.M", """ | ||
{ | ||
// Code size 76 (0x4c) | ||
.maxstack 5 | ||
.locals init (System.Span<int> V_0, //y | ||
System.Span<int> V_1) //z | ||
IL_0000: ldloca.s V_0 | ||
IL_0002: ldc.i4.1 | ||
IL_0003: newarr "int" | ||
IL_0008: dup | ||
IL_0009: ldc.i4.0 | ||
IL_000a: ldarg.0 | ||
IL_000b: stelem.i4 | ||
IL_000c: call "System.Span<int>..ctor(int[])" | ||
IL_0011: ldloca.s V_0 | ||
IL_0013: ldc.i4.0 | ||
IL_0014: call "ref int System.Span<int>.this[int].get" | ||
IL_0019: ldind.i4 | ||
IL_001a: call "void System.Console.Write(int)" | ||
IL_001f: ldloca.s V_0 | ||
IL_0021: ldc.i4.0 | ||
IL_0022: call "ref int System.Span<int>.this[int].get" | ||
IL_0027: dup | ||
IL_0028: ldind.i4 | ||
IL_0029: ldc.i4.1 | ||
IL_002a: add | ||
IL_002b: stind.i4 | ||
IL_002c: ldloca.s V_1 | ||
IL_002e: ldc.i4.1 | ||
IL_002f: newarr "int" | ||
IL_0034: dup | ||
IL_0035: ldc.i4.0 | ||
IL_0036: ldarg.0 | ||
IL_0037: stelem.i4 | ||
IL_0038: call "System.Span<int>..ctor(int[])" | ||
IL_003d: ldloca.s V_1 | ||
IL_003f: ldc.i4.0 | ||
IL_0040: call "ref int System.Span<int>.this[int].get" | ||
IL_0045: ldind.i4 | ||
IL_0046: call "void System.Console.Write(int)" | ||
IL_004b: ret | ||
} | ||
"""); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -359,6 +359,77 @@ void assertAttributeData(string name) | |
} | ||
} | ||
|
||
[Fact] | ||
public void Span_SingleElement_TempsAreNotReused() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we file a bug to follow up on this? This is a place where future optimizations could very much benefit. In this particular case the win is pretty minor. It's just an extra There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
var source = """ | ||
using System; | ||
|
||
class Program | ||
{ | ||
static void Main() | ||
{ | ||
M(1); | ||
M(2); | ||
} | ||
|
||
static void M(params Span<int> span) | ||
{ | ||
Console.Write(span[0]); | ||
Console.Write(span.Length); | ||
} | ||
} | ||
"""; | ||
|
||
var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe, expectedOutput: "1121"); | ||
verifier.VerifyDiagnostics(); | ||
verifier.VerifyIL("Program.Main", """ | ||
{ | ||
// Code size 29 (0x1d) | ||
.maxstack 1 | ||
.locals init (int V_0, | ||
int V_1) | ||
IL_0000: ldc.i4.1 | ||
IL_0001: stloc.0 | ||
IL_0002: ldloca.s V_0 | ||
IL_0004: newobj "System.Span<int>..ctor(ref int)" | ||
IL_0009: call "void Program.M(params System.Span<int>)" | ||
IL_000e: ldc.i4.2 | ||
IL_000f: stloc.1 | ||
IL_0010: ldloca.s V_1 | ||
IL_0012: newobj "System.Span<int>..ctor(ref int)" | ||
IL_0017: call "void Program.M(params System.Span<int>)" | ||
IL_001c: ret | ||
} | ||
"""); | ||
|
||
verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe, expectedOutput: "1121"); | ||
verifier.VerifyDiagnostics(); | ||
verifier.VerifyIL("Program.Main", """ | ||
{ | ||
// Code size 41 (0x29) | ||
.maxstack 4 | ||
IL_0000: ldc.i4.1 | ||
IL_0001: newarr "int" | ||
IL_0006: dup | ||
IL_0007: ldc.i4.0 | ||
IL_0008: ldc.i4.1 | ||
IL_0009: stelem.i4 | ||
IL_000a: newobj "System.Span<int>..ctor(int[])" | ||
IL_000f: call "void Program.M(params System.Span<int>)" | ||
IL_0014: ldc.i4.1 | ||
IL_0015: newarr "int" | ||
IL_001a: dup | ||
IL_001b: ldc.i4.0 | ||
IL_001c: ldc.i4.2 | ||
IL_001d: stelem.i4 | ||
IL_001e: newobj "System.Span<int>..ctor(int[])" | ||
IL_0023: call "void Program.M(params System.Span<int>)" | ||
IL_0028: ret | ||
} | ||
"""); | ||
} | ||
|
||
[Fact] | ||
public void String() | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the re-use policies on these temps? Basically is there any chance that the temp slot allocated here will be re-used or is it considered a temp that lives for the lifetime of the current method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the local slot can be reused outside the containing block, but that's fine, since the span value that references the temp can't escape outside the containing block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Some of our temp are statement level. I agree block level temp is fine but we should be sure which this is. Had other bugs with statement temps being reused in span before
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test. It doesn't look like the temp is reused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure the test is sufficient here. Looking at the code it seems that it's a re-usable temp. The default kind of the temp is
SynthesizedLocalKind.LoweringTemp
and that is not a long lived temp. The doc mentions these cannot live across a statement boundaryThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slots can be reused for locals that go out of scope. The scope is defined by blocks and sequences that list locals they own. If I remember correctly, sometimes scope of locals is extended by codegen, when, for example, a sequence returns a ref to a local that it owns. One might say the bound tree violates scoping rules in such cases, but for whatever reason a decision was made to handle the case instead of enforcing correctness of the tree.
That said, symbols for temps are never reused by default. One might, of course, intentionally create a bound tree that shares the same temp for different purposes.
I hope that helps with concerns that prompted the original question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the short-lived/long-lived story. According to my understanding, these are mostly about PDB/ENC, and the statement boundary the comment is talking about is in terms of syntax (perhaps talking in terms of sequence point boundaries would be more accurate), not about bound statement nodes. Reuse of slots in IL is not based on that. It is based on what I said in the previous message on this thread. So as long as the local is added to the right BoundBlock/BoundSequence, it will not be reused while code for that node is emitted.
There is, however, something interesting going on with where we put inline array locals. The line
locals.Add(inlineArrayLocal.LocalSymbol);
below. According to comments on_additionalLocals
field, the local is going to end up on the enclosing method outermost block. Hopefully that is not going to mess with EnC too much because effectively the local crosses a sequence point boundary. "Collection expressions" devs might want to take a close look at this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Thansk for the explanation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logged #73246 based on this comment and offline discussion.