Skip to content
This repository has been 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
merged 1 commit into from
Apr 9, 2019

Conversation

briansull
Copy link

@briansull briansull commented Apr 4, 2019

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

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.

Copy link
Author

@briansull briansull Apr 5, 2019

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.

Copy link
Author

@briansull briansull Apr 5, 2019

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

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
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?

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

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

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
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.

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
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
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)

Copy link
Author

@briansull briansull Apr 5, 2019

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

cc @dotnet/jit-contrib.

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

@briansull
Copy link
Author

I will investigate creating a CoreCLR repro test case

@mikedn
Copy link

mikedn commented Apr 5, 2019

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
Copy link
Author

briansull commented Apr 5, 2019

Thank you @mikedn

@briansull
Copy link
Author

Test case added

@briansull
Copy link
Author

PTAL @dotnet/jit-contrib.

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

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?

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
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
Author

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

@briansull
Copy link
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
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

Copy link

@CarolEidt CarolEidt left a comment

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

{
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

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