dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.92k stars 786 forks source link

Support compiler-specific statically-resolved 'when' syntax with 'enum<_>' and possibly others #9594

Closed abelbraaksma closed 2 years ago

abelbraaksma commented 4 years ago

Is your feature request related to a problem? Please describe. By trying to remove boxing for the string function, I got blocked by the fact that I cannot special-case enums. In fact, the static when syntax is quite limited, and probably rightly so, but I would like to see if it is possible to extend this with support for enum<_>, so that we can distinguish between enums and integral types.

Describe the solution you'd like I'd like to allow syntax like:

let myFunction (x: 'T) =
   doSomething x
   when 'T: enum<_> = doSomethingWithEnum

Alternatively, we can use enum without type spec. But I figured, since normal generic type restrictions are possible with enum<'T> where 'T is an integral type, this may be easier to add to the language.

Like with the normal enum type restriction, after the equals-sign, x has type annotation requires enum.

Describe alternatives you've considered I don't think there's an alternative that allows different code paths to be statically resolved for integral types (that are not enum) and enums (of an integral type).

Additional context This restriction was discussed at some length in https://github.com/dotnet/fsharp/pull/9549. If there's another workaround, I'll gladly take it ;). Since this is an "internal to the compiler only" request, I don't think it ought to go through the language-suggestions, but I've no problem to re-post there ;).

I looked around in the compiler code where this static when clause was resolved, but failed to find it (it's not easy to search for "when", it's omnipresent).

I think the only "special" type allowed is struct. Others, that are allowed in generics and SRTP, like not struct, enum<_>, delegate<_, _> are not available in this context.

Benefit of implementing this would be that we can write more targeted IL code for FSharp.Core functions. But whether it is worth the effort if the only case is to prevent boxing for string remains to be seen.

cartermp commented 4 years ago

I think in general we would like to avoid appending to the list of things you can only reasonably do in FSharp.Core. I'd like @dsyme's perspective on this, though.

abelbraaksma commented 4 years ago

you can only reasonably do in FSharp.Core

Just to make sure: I meant this feature to be added and stay within Core, I don't mean to make the compiler specific optimization syntax available outside it.

(and probably only if it's relatively trivial to add, so we can improve optimizations, otherwise it's likely not worth the effort)

TIHan commented 4 years ago

@cartermp This may be worth doing in order to handle ambiguities of enums and int-types when doing static optimizations in FSharp.Core. @abelbraaksma and I just walked through his string PR: https://github.com/dotnet/fsharp/pull/9549, that is getting rid of a lot of the boxing except for int-types because they need to be handled using IFormattable.

Even though this is internal to FSharp.Core, this may warrant a RFC because any additional constraints added to static optimizations will have to be added to the TypedTreePickle. This means it is public and the compiler understands it; you just can't write one in your own library code.

cartermp commented 4 years ago

Makes sense!

TIHan commented 4 years ago

From a brief look at how static optimizations work, it should be fairly straight forward to add.

abelbraaksma commented 4 years ago

I've the intention to work on optimizations for some of the array functions. If we consider adding enum here, we might want to consider when 'T: unmanaged as well. There's currently no way to test for a struct that has no reference types, and recently the runtime added optimizations specifically for that case (optimizations that only work with blittable types). Without unmanaged such optimizations are bound to be restricted to the integral types, plus bool, decimal, float and float32.

dsyme commented 4 years ago

@TIHan I believe this would mostly require additions to the TypedTree format, which would rule out using them in FSHarp.Core

abelbraaksma commented 4 years ago

@dsyme, are we talking about the same things here? This is an additive suggestion for static optimization syntax, which currently is only allowed in FSharp.Core.

thinkbeforecoding commented 2 years ago

I'm ok to implement it if someone guides me where current version is implemented. SR when syntax on interfaces would also enable perf gain in comparison operators which currently are slow with IComparable on structs.

abelbraaksma commented 2 years ago

@thinkbeforecoding great if you want to take a look at this! It's been a while that I looked at this, but this is related to a certain syntax for when that gets statically compiled if and only if the thing being compiled is FSharp.Core. In other words, this is syntax that allows making optimizations to functions in FSharp.Core that are otherwise impossible. When the compiler encounters such "static when" clauses (can be either when 'T or when ^T syntax iirc), it will compile that function as an inline function for only that when-path (i.e. when the function is called from user code), which allows great optimizations.

I looked into this at the time and I don't think it was terribly hard, but I do recall it was buried pretty deep in the compiler optimization code. But @dsyme may be more suited in pointing you to the right direction ;).

thinkbeforecoding commented 2 years ago

@dsyme could you give me some pointers to files/functions that handle this?

dsyme commented 2 years ago

I would start with WhenTyparIsStruct and add WhenTyparIsEnum and continue from there

https://github.com/dotnet/fsharp/blob/main/src/Compiler/SyntaxTree/SyntaxTree.fsi#L988

I'll actually close this issue however, as it needs a suggestion and RFC. Please add one?

To actually use this in FSharp.Core is tricky as old compilers must consume new FSHarp.Core. If we could somehow find a backwards compat way of accpeting the constructs then we could probably add this sort of thing. That would probably mean immediately adding a clause in TypedTreePickle.fs that ignores static optimization instructions it doesn't recognise, assuming they are from the future. That would set us up to be backwards compat eventually. Not sure there's any other way to do it that doesn't involve a lot of engineering