Open gimbling-away opened 7 months ago
But...SRTP is statically resolved, hence has to be inlined.
See some discussions below
But...SRTP is statically resolved, hence has to be inlined.
Or I should say, that the way they designed and functioning, they require to be inlined now. I would say it's one of the those things which was decided when designing F# 1.0.
I'm not entirely sure how can traits be expressed in runtime for them to universally work without inlining.
Or a bunch of functions will have to be codegen'd and statically dispatched on the callsite.
But...SRTP is statically resolved, hence has to be inlined.
I can't seem to connect the dots here, can the compiler not generate different copies of the function for different TPs? That's how many languages do it (For ex. Rust)
But...SRTP is statically resolved, hence has to be inlined.
I can't seem to connect the dots here, can the compiler not generate different copies of the function for different TPs? That's how many languages do it (For ex. Rust)
Replied just before you posted it. This will work for sure. However, I can see some issues with pickled data compatibility (since new compiler will have to suppor both, as well as generate both traits, so old compilers know about it as well).
But...SRTP is statically resolved, hence has to be inlined.
I can't seem to connect the dots here, can the compiler not generate different copies of the function for different TPs? That's how many languages do it (For ex. Rust)
Are you sure it is worth it? And are you sure it’s difficulty is M?
Are you sure it is worth it? And are you sure it’s difficulty is M?
ppprobably not? (with @vzarytovskii's mention of the compiler needing to support two variants of trait data) — I haven't worked with FSC before, so had no idea. Could bump it up to L/XL perhaps?
@vshapenko the idea is interesting and worth existing here.
One aspect is code size, and enabling features of SRTP, without forcing inline.
Other compilers (C++ and Rust, I'd gather) handle this, so there must be good reasons.
Is it high importance for F#, today? not for me.
Could bump it up to L/XL perhaps?
I think it's fine to leave it
I think the actual problem here is in the "S" of SRTP. The use of inline
is just there to statically resolve the parameters during compile time (static).
To make that dynamic, are you suggestion to let SRTP work like dynamic method calls, as in C#? Something like Foo?doSomething()
, where doSomething
is in this case in the SRTP signature?
Because, you know, one of the most powerful reasons that SRTP works the way it does is that during compile time it can guarantee that the method is there, and furthermore, it will embed that method.
Or am I misreading this and do you still want static resolve (during compile time), but not embedded on the call site, instead just like a function call with parameters? (your mention of "smaller binaries", which may not be a given, btw, suggests this).
Example:
let foo () =
let inline f a b = a + b // SRTP
let inline g a b = a + b // SRTP
let x = f 10 20
let y = g 60.0 70.0
x * int y
Currently, this looks like this after compilation (note that you also get const folding):
.method public static
int32 foo () cil managed
{
.maxstack 4
.locals init (
[0] float64 y
)
IL_0000: nop
IL_0001: ldc.r8 60
IL_000a: ldc.r8 70
IL_0013: add ; inlined g 60 70
IL_0014: stloc.0
IL_0015: ldc.i4.s 30 ; inlined and const-folded f 10 20
IL_0017: ldloc.0
IL_0018: conv.i4 ; cast
IL_0019: mul ; multiply
IL_001a: ret
}
Removing inline
to mimic the behavior of your suggestion (but keeping the SRTP semantics of f
)
.method public static
int32 foo () cil managed
{
.maxstack 4
.locals init (
[0] int32 x,
[1] float64 y
)
IL_0000: ldc.i4.s 10
IL_0002: ldc.i4.s 20
IL_0004: call int32 Tests::f(int32, int32) ; f 10 20 (no inlining)
IL_0009: stloc.0
IL_000a: ldc.r8 60
IL_0013: ldc.r8 70
IL_001c: call float64 Tests::g(float64, float64) ; g 60.0 70.0 (no inlining)
IL_0021: stloc.1
IL_0022: ldloc.0
IL_0023: ldloc.1
IL_0024: conv.i4 ; cast
IL_0025: mul ; multiply
IL_0026: ret
}
To get the second output, I had to compile it in debug mode, as the F# optimizations will inline it anyway, which begs the question how much you would really gain here: it may result in larger IL code, larger overhead and/or slightly longer JIT compile times, and possibly slower execution, OR, in certain cases, the result may be exactly the same due to existing optimizations.
There's one more thing to consider. You may have noticed that I wrote two functions f
and g
that do the same thing. That was on purpose. With inline
you get auto-generalization and you can use a single function with these arguments. Without it, you lose that ability, and it will bind to the first type used, hence the two functions.
Which suggests to me that we may keep the keyword inline
for keeping the SRTP semantics the same, but add perhaps an attribute [<DoNotInline>]
or NoInlining(true)
to the function, but that'll look rather silly and confusing...
I propose we allow non-inline SRTP functions
The existing way of approaching this problem in F# is ... Functions that use SRTPs are forced to be
inline
, which is not idealPros and Cons
The advantages of making this adjustment to F# are Smaller binaries, recursive SRTP usage
The disadvantages of making this adjustment to F# are None, as of now.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): M
Related suggestions: (put links to related suggestions here)
Affidavit (please submit!)
Please tick these items by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.