Skip to content

Redefine 'not' to not emit IL directly - #9434

Closed
cartermp wants to merge 1 commit into
dotnet:masterfrom
cartermp:no-il-not
Closed

Redefine 'not' to not emit IL directly#9434
cartermp wants to merge 1 commit into
dotnet:masterfrom
cartermp:no-il-not

Conversation

@cartermp

@cartermpcartermp commented Jun 12, 2020

Copy link
Copy Markdown
Contributor

Fixes#9433

The current implementation of not gets funky when you pass in a call to isNull for a reference type. Probably other quirks too. The following benchmark shows the effect:

moduleOp =let inlinenot' x =if x thenfalseelsetruelet inlinenot'' x =match x withtrue->false|false->true[<MemoryDiagnoser>]typeNotBench()=lets="hello"[<Benchmark(Baseline=true)>]member_.Not()=let mutableb=falsefor i=0to1do b <-not(isNull s)
b
[<Benchmark>]member_.NotWithIf()=let mutableb=falsefor i=0to1do b <- Op.not' (isNull s)
b
[<Benchmark>]member_.NotWithMatch()=let mutableb=falsefor i=0to1do b <- Op.not'' (isNull s)
b

I get these timings against .NET 5:

MethodMeanErrorStdDevRatioGen 0Gen 1Gen 2Allocated
Not1.859 ns0.0224 ns0.0187 ns1.00----
NotWithIf1.277 ns0.0178 ns0.0139 ns0.69----
NotWithMatch1.277 ns0.0188 ns0.0157 ns0.69----

And these for net48:

MethodMeanErrorStdDevRatioRatioSDGen 0Gen 1Gen 2Allocated
Not1.909 ns0.0448 ns0.0419 ns1.000.00----
NotWithIf1.295 ns0.0357 ns0.0334 ns0.680.03----
NotWithMatch1.292 ns0.0126 ns0.0118 ns0.680.02----

@forki

forki commented Jun 12, 2020

Copy link
Copy Markdown
Contributor

What happens if you just use if/then? Pattern matching looks really ugly on bool

@cartermp

Copy link
Copy Markdown
ContributorAuthor

oh poopy, failures

@cartermp

cartermp commented Jun 12, 2020

Copy link
Copy Markdown
ContributorAuthor

if/then emits the same IL (and same benchmark results), I can change that

@abelbraaksma

abelbraaksma commented Jun 12, 2020

Copy link
Copy Markdown
Contributor

Hey thanks, that was quick! I thought, since the inline assembly is the same, that something went wrong in the optimizer. But this definitely fixes the general case.

Btw, I've meanwhile found that the issue happens with other functions too, but they have to be small, and have to involve null. Tx for the quick fix!

@cartermpcartermp changed the title Redefine 'not' to just use pattern matchingRedefine 'not' to not emit IL directlyJun 12, 2020
@cartermp

Copy link
Copy Markdown
ContributorAuthor

So running the baseline update tool updates a lot more than just what was failing, and some of what it updates are more than just comments. I'm also curious why some of the IL has more fields than before.

@cartermp

Copy link
Copy Markdown
ContributorAuthor

I think I might just move these tests to the new test suite because it's horrible trying to update anything to do with fsharpqa baselines

@TIHan

Copy link
Copy Markdown
Contributor

@cartermp Because the IL output is more complex, we should think about ways to reduce it, even if the runtime does folding.

@cartermp

Copy link
Copy Markdown
ContributorAuthor

@TIHan I'm not sure what's up. I don't see that same IL in sharplab's IL viewer, though: https://sharplab.io/#v2:DYLgZgzgNALiCWwA+wCmMAE8B2weowFsBPAOQHtMAKADxAwCNzzgBKDAXizAxoxgAWqbBjABDYBAKpJBGACcArqiA===

.class public auto ansi abstract sealed _
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = (
01 00 07 00 00 00 00 00
)
// Methods
.method public static valuetype [System.Private.CoreLib]System.Boolean myNot (
valuetype [System.Private.CoreLib]System.Boolean x
) cil managed {
// Method begins at RVA 0x2050
// Code size 5 (0x5)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: ceq
IL_0004: ret
} // end of method _::myNot
} // end of class _

Could there be a problem in the tool used to generate baselines?

@baronfel

Copy link
Copy Markdown
Member

Sharplab also depends on FCS for this, and if they are behind or there are codegen updates that haven't been released that could be causing their output to be out of sync with what you expect.

Comment threadsrc/fsharp/FSharp.Core/prim-types.fs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is probably the source of my woes w.r.t IL baselines, I wonder why though

@abelbraaksma

Copy link
Copy Markdown
Contributor

Strangely, if you look at the function's definition in ilspy, both are equal. Only when used with a small function that compares to null, the change is apparent in the generated IL. But that was precisely why we did this change.

(though I admit I don't understand the issue with the baselines, perhaps a test expects the old IL output?)

@cartermp

Copy link
Copy Markdown
ContributorAuthor

git is hard, closing

@abelbraaksma

abelbraaksma commented Jul 19, 2020

Copy link
Copy Markdown
Contributor

Follow-up: I've made a PR with a new attempt in #9715, which uses a slightly different approach than this one, which leads to less changes in the baseline tests (actually, only one). It appears to work now :).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

not(isNull x) leads to very odd and partially unreachable IL code that performace 5x slower than redefining not yourself

5 participants

@cartermp@forki@abelbraaksma@TIHan@baronfel