servicetitan / Stl.Fusion

Build real-time apps (Blazor included) with less than 1% of extra code responsible for real-time updates. Host 10-1000x faster APIs relying on transparent and nearly 100% consistent caching. We call it DREAM, or Distributed REActive Memoization, and it's here to turn real-time on!
MIT License
1.82k stars 106 forks source link

Stl.Interception gets trimmed away #665

Open AliveDevil opened 7 months ago

AliveDevil commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

Hi, yes, there was no IL trimming markup.

I've just added one: https://github.com/servicetitan/Stl.Fusion/commit/12e586d8636a9c0d337f75c02a32a1ecd2626f3f

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 commented 7 months ago

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

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 commented 7 months ago

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 commented 7 months ago

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":

AliveDevil commented 7 months ago

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 commented 7 months ago

Nice, will check it.

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

alexyakunin commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

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.