Skip to content
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

Stl.Interception gets trimmed away #665

Open
AliveDevil opened this issue Nov 17, 2023 · 10 comments
Open

Stl.Interception gets trimmed away #665

AliveDevil opened this issue Nov 17, 2023 · 10 comments

Comments

@AliveDevil
Copy link
Contributor

Trying to get Stl.Interception TypedFactory to not get trimmed away in .NET 8.
image

Might need some C# 12 features to get working correctly: Interceptors, such that the proxy is known at compile time and isn't stripped away completely.

@AliveDevil
Copy link
Contributor Author

Not quite sure when I can take a moment to implement this, but trimming reduces the self-contained single file publish from 90 MiB to 20 MiB.

@alexyakunin
Copy link
Collaborator

Hi, yes, there was no IL trimming markup.

I've just added one: 12e586d

The downside is: prob. it still won't work, coz interceptor types aren't referenced. One solution would be to generate module initializers to "hold" them.

For now you can manually reference these types - try TypeExt.MarkUsed<T>(), I added it for this purpose.

@alexyakunin
Copy link
Collaborator

alexyakunin commented Nov 19, 2023

Honestly, I don't understand why they even came up with NativeAOT:

  • Static analysis isn't good enough to resolve even simple things - e.g. you pass ICommand<T> & want to construct CommandContext<T>. How can you do this, assuming you pass TCommand or ICommand (base type)?
  • Stl.Rpc & Fusion use way more complex transforms in some cases - e.g. RpcInboundCall<TResult> is used for any incoming call with result type TResult; ArgumentList<TArg1, TArg2, CancellationToken> is transformed to ArgumentList<TArg1, TArg2>, etc. - all these decisions are made relying on Reflection.
  • IL Emit is used in a decent number of scenarios as well - e.g. to generate fast getters for properties which aren't known at compile time.
  • Things like DbEntityResolver use expression trees to build pretty advanced queries.
  • Etc.

In other words, I don't see how purely static AOT is even usable anywhere but in some toy projects.

What they should really focus on is profile-guided AOT - AOT is almost solely about startup time, and all you need is to know what runs on startup. And it's easy - just run & record it. And it's 100% fine if you compile just this & interpret or JIT-compile the rest - in fact, they do exactly this on MAUI Android.

One other thing with NativeAOT is: you may try to play with it if you start something new. But imagine a huge app that's already there - what kind of mindset you are supposed to have to decide to try it, if it's a huge investment, and "all or nothing" in the end, but most likely "nothing"? Just look at these 2.5K of code in above commit - what I was doing is ~ "ok, let's do the best we can & see what stuff I can't resolve statically". And I can name like 10+ pretty complex scenarios I simply couldn't cover (some are listed above). And that's for a single library.

Long story short... IDK who came up with NativeAOT idea, but this guy definitely doesn't know how real-world apps look like.

@alexyakunin
Copy link
Collaborator

I kinda angry about this b/c they invest so much into this crap vs focusing on what really matters - i.e. profile-based AOT. All this shit with [DynamicallyAccessedMembers] no one really wants to see - why, why to even pollute the code with all of that stuff, if it doesn't really work?

And it's the same about trimming. Just decide what you want to keep in AOT vs MSIL form based on stored startup profile, that's it!

@alexyakunin
Copy link
Collaborator

alexyakunin commented Nov 19, 2023

And it would be way more useful if, instead of focusing on what to trim, they'd focus on how to split the code into 3 "pieces":

  • Absolutely necessary (comes as AOT/native code, loaded before app startup)
  • Reachable in 2-3 hops (comes as MSIL, loaded before app startup)
  • Maybe necessary (comes as MSIL, loaded in the background after your app starts).

@AliveDevil
Copy link
Contributor Author

AliveDevil commented Nov 19, 2023

Think I've struck a nerve here 😅

For now you can manually reference these types - try TypeExt.MarkUsed<T>(), I added it for this purpose.

Nice, will check it.

And it would be way more useful if, instead of focusing on what to trim, they'd focus on how to split the code into 3 "pieces":

That's been tried with ReadyToRun, and requires double the application size for both the managed and AOT compiled bits - which imo wasn't viable from the beginning.

why, why to even pollute the code with all of that stuff, if it doesn't really work?

Checked the commit, and I do agree: That's just a burden.

I'm not that involved in the wider .NET core world, as I'm still dependent on .NET framework … I might be influenced by shiny new things for my personal/hobby projects though - and may very well run that specific app with --self-contained -p:PublishSingleFile=True without ever enabling NativeAot or trimming (the target environment this app should run on isn't constrained in any way).

Just to confirm for future enhancements: All-of Stl should be trimming-friendly, not just parts of it.

I'm fine with a non-solution closure, just to keep this topic in here for future reference.

@alexyakunin
Copy link
Collaborator

Nice, will check it.

[DynamicDependency(...All, type(...))] actually does the same, I removed the method :)

@alexyakunin
Copy link
Collaborator

Just to confirm for future enhancements: All-of Stl should be trimming-friendly, not just parts of it.

Speaking of which, I just added [DynamicDependency] to every builder indicating what should be kept. So now you should prob. just reference proxy types with [DynamicDependency] from somewhere, later I'll start generating a module initializer for these types to make sure they're kept no matter what.

@alexyakunin
Copy link
Collaborator

alexyakunin commented Nov 20, 2023

As for NativeAOT, the main issue is that Reflection and IL emit is broken there. So all they need is to add an interpreter to handle this - it's kinda fine in most of scenarios, even if it's slower.

@AliveDevil
Copy link
Contributor Author

I see. So the generated proxy still gets trimmed, but the factory interceptor is kept in the trimmed build.

Module initializer is definitely the easiest solution here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants