-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
[.NET 8] <PublishTrimmed> no longers works but throws build process for an infinite loop #94831
Comments
Tagging subscribers to this area: @agocke, @sbomer, @vitek-karas Issue DetailsDescriptionSo far I built my ARM64 binary by Here are my project settings:
Turns out What's going wrong and how could I trim my build as before? Reproduction StepsPlease see above Expected behaviorTrimming should work with .NET8 as it worked with .NET 7 Actual behaviorTrimming does not work with .NET8 as it worked with .NET 7 Regression?Yes Known WorkaroundsNone ConfigurationNo response Other informationNo response
|
Thanks for reporting this. Unfortunately, there's not enough info here to figure out what's going on. Could you please try some of these:
|
Thanks for getting back to me @vitek-karas. Curious: would it help if I attached the untrimmed binary so you could try running the trim logic on it? |
We would need all of the input binaries and the trimmer command line. You could try using https://github.com/vitek-karas/illinkrepro. Basically you run your publish with |
Thanks @vitek-karas. To clarify:
Could I just attach that Note: I already had quite a few support cases/issues with the great Platform UNO team, where I happily shared BINLOG files. That's why I wouldn't have any concern on sharing that file. Please let me know if you felt that would be a way to go or if I rather should go through the steps you outlined above. Thanks |
Small correction:
The binlog is unfortunately not enough - it contains the command line to the trimmer, but not the input assemblies. For that something like the tool from the We can also arrange for a more private way of sending this data, if you want. The best way then is to create a Visual Studio Feedback item and attach the data there. |
Thanks @vitek-karas. I just created a repo and filed it as Visual Studio Feedback item. Side note: It would make the handling of your illinkrepo tool more handy if just a single, self-contained file would be created. I had to copy the illinkrepo.* files + all dlls to the target directory. Please let me know if you needed anything else. |
@DierkDroth thanks a lot for the repro - I was able to investigate. @sbomer for ideas about fixing: The problem is a combination of the interprocedural data flow and a limit in The repro for this is a very large state machine. The A "Simple" repro looks like: [ExpectedWarning ("IL2072", CompilerGeneratedCode = true)]
static async void VeryLargeStateMachine()
{
Type t = GetWithPublicFields ();
// 100
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
// 200
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
// 300
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync (); await MethodAsync ();
t.RequiresAll ();
} |
As for the potential fix... the only idea I was able to come up with is to:
|
Thanks for your feedback @vitek-karas. I'm happy to help and delighted that you had been able to isolate the cause of trouble. Curious: you mentioned 'exponential state expansion'. Would you consider my app as being rather 'big', that this scenario was not uncovered before?!? |
@vitek-karas thanks for investigating. I agree with your assessment - it didn't occur to me at the time that resetting the values in the meet operation breaks the invariant that meet should always move down the lattice. I agree we should add a special "overflow" (or "bottom") value with the meet behavior you described. Maybe it would be better to treat it as an unknown value (treat it as a pattern we couldn't analyze and produce a warning) instead of skipping it. |
Your app is not that big, although there is one method in it which is very large and has LOT of async calls in it. That's what caused the problem. But overall it's not that crazy ("only ~500 await calls"). The exponential state problem is that the tooling simulates all potential values a variable can take in the method, and it's very easy to blow that set out of the water. For example: typeof (TestType).RequiresAll (); // Force data flow analysis
object[] data = new object[20];
if (true) data[0] = new object ();
if (true) data[1] = new object ();
if (true) data[2] = new object ();
if (true) data[3] = new object ();
if (true) data[4] = new object ();
if (true) data[5] = new object ();
if (true) data[6] = new object ();
if (true) data[7] = new object ();
if (true) data[8] = new object ();
if (true) data[9] = new object ();
if (true) data[10] = new object ();
if (true) data[11] = new object ();
if (true) data[12] = new object ();
if (true) data[13] = new object ();
if (true) data[14] = new object ();
if (true) data[15] = new object ();
if (true) data[16] = new object ();
if (true) data[17] = new object ();
if (true) data[18] = new object ();
if (true) data[19] = new object (); Now just assume that tool is not smart enough to figure out the condition, that it has to assume in each if both branches can be taken. Then the number of possible states for this test is on the order of 2^20. The tool obviously can't represent it in memory, so we have to "give up" at some point. |
@sbomer |
#87634 introduced a limit on the number of values tracked in dataflow analysis, but this approach was incorrect because resetting the set of tracked values was effectively moving up the dataflow lattice, breaking the invariant and causing potential hangs. The hang was observed in #94831, where (as found by @vitek-karas) the hoisted local `state` field of an async method with many await points was getting a large number of tracked values, hitting the limit, being reset to "empty", but then growing again, causing the analysis not to converge. The fix is to instead introduce a special value `ValueSet<TValue>.Unknown` that is used for this case. It acts like "bottom" in the lattice, so that it destroys any other inputs on meet ("bottom" meet anything else is "bottom"). In this testcase the `state` field isn't involved in dataflow warnings, so this actually allows the method to be analyzed correctly, producing the expected warning for the `Type` local that flows across all of the await points. Fixes #94831
…et#95041) dotnet#87634 introduced a limit on the number of values tracked in dataflow analysis, but this approach was incorrect because resetting the set of tracked values was effectively moving up the dataflow lattice, breaking the invariant and causing potential hangs. The hang was observed in dotnet#94831, where (as found by @vitek-karas) the hoisted local `state` field of an async method with many await points was getting a large number of tracked values, hitting the limit, being reset to "empty", but then growing again, causing the analysis not to converge. The fix is to instead introduce a special value `ValueSet<TValue>.Unknown` that is used for this case. It acts like "bottom" in the lattice, so that it destroys any other inputs on meet ("bottom" meet anything else is "bottom"). In this testcase the `state` field isn't involved in dataflow warnings, so this actually allows the method to be analyzed correctly, producing the expected warning for the `Type` local that flows across all of the await points. Fixes dotnet#94831
@sbomer thanks for working on that issue. Curious: would this result in a .NET8 update? If so, what would be the expected timeframe? |
Excellent! Thanks for your feedback @sbomer |
…) (#95302) #87634 introduced a limit on the number of values tracked in dataflow analysis, but this approach was incorrect because resetting the set of tracked values was effectively moving up the dataflow lattice, breaking the invariant and causing potential hangs. The hang was observed in #94831, where (as found by @vitek-karas) the hoisted local `state` field of an async method with many await points was getting a large number of tracked values, hitting the limit, being reset to "empty", but then growing again, causing the analysis not to converge. The fix is to instead introduce a special value `ValueSet<TValue>.Unknown` that is used for this case. It acts like "bottom" in the lattice, so that it destroys any other inputs on meet ("bottom" meet anything else is "bottom"). In this testcase the `state` field isn't involved in dataflow warnings, so this actually allows the method to be analyzed correctly, producing the expected warning for the `Type` local that flows across all of the await points. Fixes #94831
Description
So far I built my ARM64 binary by
dotnet publish -c Release -r linux-arm64 -p:PublishSingleFile=true -p:SelfContained=true -p:PublishReadyToRun=true MyProject.csproj
Here are my project settings:
Turns out
<PublishTrimmed>true</PublishTrimmed>
no longers works: the build which tools ~3 minutes on .NET 7 now did not terminate the 'optimization phase' after > 9 hours.What's going wrong and how could I trim my build as before?
Reproduction Steps
Please see above
Expected behavior
Trimming should work with .NET8 as it worked with .NET 7
Actual behavior
Trimming does not work with .NET8 as it worked with .NET 7
Regression?
Yes
Known Workarounds
None
Configuration
No response
Other information
No response
The text was updated successfully, but these errors were encountered: