Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Block the hoisting of TYP_STRUCT rvalues in loop hoisting#23739

Merged
briansull merged 1 commit into
dotnet:masterfrom
briansull:struct-cse
Apr 9, 2019
Merged

Block the hoisting of TYP_STRUCT rvalues in loop hoisting#23739
briansull merged 1 commit into
dotnet:masterfrom
briansull:struct-cse

Conversation

@briansull

@briansull briansull commented Apr 4, 2019

Copy link
Copy Markdown

PR #22255 introduced test regressions:

Assertion failed 'tree->gtHasReg()' in 'Program:Test(ref):this' (IL size 80)

@briansull briansull changed the title Only allow structs whose storage can be held in a register to be CS candidates Only allow structs whose storage can be held in a register to be CSE candidates Apr 5, 2019
Comment thread src/jit/optcse.cpp Outdated
structPassingKind howToPassStruct;
var_types structBaseType = getArgTypeForStruct(structHnd, &howToPassStruct, false, size);

// Only allow structs whose storage can be held in a register to be CSE candidates.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Presumably that's just because it's not very useful to CSE something that doesn't fit in a register. Or is there a correctness issue as well?

I suppose it would make sense to CSE a struct indir if the new temporary variable can then be promoted. But that's probably quite complicated to get right.

@briansull briansull Apr 5, 2019

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, There currently is a correctness issue as well when we perform loop hoisting we place a l-value which is a load (a GT_IND of a TYP_STRUCT ) into the loop pre-header.
This indirection doesn't get handled properly (or removed) and we end up asserting about a missing register during codegen.

@briansull briansull Apr 5, 2019

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

see bool Compiler::optHoistLoopExprsForTree()
where we currently require:

        // Tree must be a suitable CSE candidate for us to be able to hoist it.
        treeIsHoistable &= optIsCSEcandidate(tree);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah, of course, loop hoisting will hoist the LHS of the struct assignment but that does not have a struct handle so there's no way for it to actually work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still not clear to me if the above is a legality check followed by a profitability check, or if both are legality checks. Can you clarify?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@briansull - in our discussions of this, it seemed like the fundamental issues are:

  1. The struct handling in the JIT relies on the fact that a struct r-value will always be used in a context where its size and/or handle are carried by an l-value (e.g. a block node on the lhs of an assign, or a block store).
  2. An unused hoisted rvalue doesn't get removed. This leaves the problematic handle-less size-less struct indirection.

Before taking this fix, I'd like us to understand what diffs this creates, and file an issue to address the above problems.

As we improve our struct codegen, I'd expect us to see more cases where hoisting struct values would be beneficial, as we can't (and probably never will) promote all structs that might be created redundantly (within a loop or otherwise).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

An unused hoisted rvalue doesn't get removed. This leaves the problematic handle-less size-less struct indirection.

Not only that it doesn't get removed but it simply cannot be used at all. Because loop hoisting is expecting CSE to assign the hoisted tree to a variable but CSE can't do that because there's struct type information available in the r-value.

To make this work with the current loop hoisiting implementation we'd need to stop discarding struct information from r-values (converting OBJ to TYP_STRUCT IND). That's doable and IMO desirable, discarding struct information is just risky.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FWIW I have seen many examples where manual struct CSEs are profitable -- for example the before version of this did not cache Start and End as local structs. IL semantics for structs tends to lead to lots of struct copies and often many of those copies are identical.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public OffsetAndLength GetOffsetAndLength(int length)
{
int start;
Index startIndex = Start;
if (startIndex.IsFromEnd)
start = length - startIndex.Value;
else
start = startIndex.Value;
int end;
Index endIndex = End;
if (endIndex.IsFromEnd)
end = length - endIndex.Value;
else
end = endIndex.Value;
if ((uint)end > (uint)length || (uint)start > (uint)end)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm, should perhaps this fix be restricted to loop hoisting?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I have coded up an alternative fix that blocks the hoisting of TYP_STRUCT r-values.

Comment thread src/jit/optcse.cpp Outdated
structPassingKind howToPassStruct;
var_types structBaseType = getArgTypeForStruct(structHnd, &howToPassStruct, false, size);

// Only allow structs whose storage can be held in a register to be CSE candidates.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is "register" referring just to integer registers and to the lower element of vector registers (for float/double)?

I imagine we may want to support CSE (and other optimizations) for full vector registers eventually...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The call to getArgTypeForStruct should identify the register sized SIMD struct types as
(howToPassStruct == SPK_PrimitiveType)

@briansull briansull Apr 5, 2019

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Currently it doesn't appear that we support passing such vector types in registers according to getArgTypeForStruct and getPrimitiveTypeForStruct

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe @CarolEidt is working on this for the Vector ABI support.

@sandreenko

Copy link
Copy Markdown

cc @dotnet/jit-contrib.

@briansull is it possible to create a CoreClr repro based on desktop failures for this issue?

@briansull

Copy link
Copy Markdown
Author

I will investigate creating a CoreCLR repro test case

@mikedn

mikedn commented Apr 5, 2019

Copy link
Copy Markdown

Here's a repro in case you don't have one already:

    struct Str
    {
        float a, b, c, d, e, f;
    }

    class Cls
    {
        public Str s;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static void Sink<T>(ref T t)
    {
    }

    static void Main() => new Program().Test(new Cls());

    [MethodImpl(MethodImplOptions.NoInlining)]
    void Test(Cls c)
    {
        Str l1 = default;
        Str l2 = default;
        Str l3 = default;

        for (int i = 0; i < 10; i++)
        {
            l1 = c.s;
            l2 = c.s;
            l3 = c.s;
        }

        Sink(ref l1);
        Sink(ref l2);
        Sink(ref l3);
    }

asserts:

Assert failure(PID 14128 [0x00003730], Thread: 16408 [0x4018]): Assertion failed 'tree->gtHasReg()' in 'Program:Test(ref):this' (IL size 80)

    File: d:\projects\coreclr\src\jit\codegenlinear.cpp Line: 1289
    Image: D:\Projects\coreclr\bin\Product\Windows_NT.x64.Checked\CoreRun.exe

@briansull

briansull commented Apr 5, 2019

Copy link
Copy Markdown
Author

Thank you @mikedn

@briansull

Copy link
Copy Markdown
Author

Test case added

@briansull

Copy link
Copy Markdown
Author

PTAL @dotnet/jit-contrib.

@AndyAyersMS AndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this cause any diffs?

Comment thread src/jit/optcse.cpp Outdated
structPassingKind howToPassStruct;
var_types structBaseType = getArgTypeForStruct(structHnd, &howToPassStruct, false, size);

// Only allow structs whose storage can be held in a register to be CSE candidates.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still not clear to me if the above is a legality check followed by a profitability check, or if both are legality checks. Can you clarify?

@briansull

Copy link
Copy Markdown
Author

@dotnet-bot Test Windows_NT arm Cross Checked Innerloop Build and Test

@briansull

Copy link
Copy Markdown
Author

On the desktop there are no reported Asm Diffs in the frameworks.

@briansull briansull changed the title Only allow structs whose storage can be held in a register to be CSE candidates Block the hoisting of TYP_STRUCT rvalues in loop hoisting Apr 8, 2019
@briansull

Copy link
Copy Markdown
Author

I have coded up an alternative fix that blocks the hoisting of TYP_STRUCT r-values.
PTAL @CarolEidt @AndyAyersMS @dotnet/jit-contrib.

Working on diffs

@CarolEidt CarolEidt left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM - with one comment suggestion

Comment thread src/jit/optimizer.cpp Outdated
{
GenTreeCall* call = tree->AsCall();
if (call->gtCallType != CT_HELPER)
// We cannot hoist an r-value of TYP_STRUCT, as it is illegal to do so

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would reword this comment as "We cannot hoist an r-value of TYP_STRUCT, as they currently do not carry full descriptors of the struct type" or something to that effect.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

@briansull

Copy link
Copy Markdown
Author

Rebased and squashed commits

No Asm diffs

@briansull briansull merged commit cd41863 into dotnet:master Apr 9, 2019
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Block the hoisting of TYP_STRUCT rvalues in loop hoisting

Commit migrated from dotnet/coreclr@cd41863
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants