KEMBAR78
Ineffective codegen for or pattern type checking · Issue #47920 · dotnet/runtime · GitHub
Skip to content

Ineffective codegen for or pattern type checking #47920

@huoyaoyuan

Description

@huoyaoyuan

Description

Examine this code:

using System;
public class C
{
    public bool M0(object o)
    {
        return o is int or uint or long;
    }
    public bool M1(object o)
    {
        return o != null && (o is int or uint or long);
    }
    public bool M2(object o)
    {
        return o != null && (o.GetType() == typeof(int) || o.GetType() == typeof(uint) || o.GetType() == typeof(long));
    }
}

IL Compiled:

.method public hidebysig 
        instance bool M0 (
            object o
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 28 (0x1c)
        .maxstack 8

        IL_0000: ldarg.1
        IL_0001: isinst [System.Private.CoreLib]System.Int32
        IL_0006: brtrue.s IL_001a

        IL_0008: ldarg.1
        IL_0009: isinst [System.Private.CoreLib]System.UInt32
        IL_000e: brtrue.s IL_001a

        IL_0010: ldarg.1
        IL_0011: isinst [System.Private.CoreLib]System.Int64
        IL_0016: ldnull
        IL_0017: cgt.un
        IL_0019: ret

        IL_001a: ldc.i4.1
        IL_001b: ret
    } // end of method C::M0

    .method public hidebysig 
        instance bool M1 (
            object o
        ) cil managed 
    {
        // Method begins at RVA 0x206d
        // Code size 33 (0x21)
        .maxstack 8

        IL_0000: ldarg.1
        IL_0001: brfalse.s IL_001f

        IL_0003: ldarg.1
        IL_0004: isinst [System.Private.CoreLib]System.Int32
        IL_0009: brtrue.s IL_001d

        IL_000b: ldarg.1
        IL_000c: isinst [System.Private.CoreLib]System.UInt32
        IL_0011: brtrue.s IL_001d

        IL_0013: ldarg.1
        IL_0014: isinst [System.Private.CoreLib]System.Int64
        IL_0019: ldnull
        IL_001a: cgt.un
        IL_001c: ret

        IL_001d: ldc.i4.1
        IL_001e: ret

        IL_001f: ldc.i4.0
        IL_0020: ret
    } // end of method C::M1

    .method public hidebysig 
        instance bool M2 (
            object o
        ) cil managed 
    {
        // Method begins at RVA 0x2090
        // Code size 75 (0x4b)
        .maxstack 2

        IL_0000: ldarg.1
        IL_0001: brfalse.s IL_0049

        IL_0003: ldarg.1
        IL_0004: callvirt instance class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Object::GetType()
        IL_0009: ldtoken [System.Private.CoreLib]System.Int32
        IL_000e: call class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Type::GetTypeFromHandle(valuetype [System.Private.CoreLib]System.RuntimeTypeHandle)
        IL_0013: call bool [System.Private.CoreLib]System.Type::op_Equality(class [System.Private.CoreLib]System.Type, class [System.Private.CoreLib]System.Type)
        IL_0018: brtrue.s IL_0047

        IL_001a: ldarg.1
        IL_001b: callvirt instance class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Object::GetType()
        IL_0020: ldtoken [System.Private.CoreLib]System.UInt32
        IL_0025: call class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Type::GetTypeFromHandle(valuetype [System.Private.CoreLib]System.RuntimeTypeHandle)
        IL_002a: call bool [System.Private.CoreLib]System.Type::op_Equality(class [System.Private.CoreLib]System.Type, class [System.Private.CoreLib]System.Type)
        IL_002f: brtrue.s IL_0047

        IL_0031: ldarg.1
        IL_0032: callvirt instance class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Object::GetType()
        IL_0037: ldtoken [System.Private.CoreLib]System.Int64
        IL_003c: call class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Type::GetTypeFromHandle(valuetype [System.Private.CoreLib]System.RuntimeTypeHandle)
        IL_0041: call bool [System.Private.CoreLib]System.Type::op_Equality(class [System.Private.CoreLib]System.Type, class [System.Private.CoreLib]System.Type)
        IL_0046: ret

        IL_0047: ldc.i4.1
        IL_0048: ret

        IL_0049: ldc.i4.0
        IL_004a: ret
    } // end of method C::M2

codegen:

C.M0(System.Object)
    L0000: mov rax, rdx
    L0003: test rax, rax
    L0006: je short L0017
    L0008: mov rcx, 0x7ff9125bb258
    L0012: cmp [rax], rcx
    L0015: jne short L001c
    L0017: test rdx, rdx
    L001a: jne short L0058
    L001c: mov rax, rdx
    L001f: test rax, rax
    L0022: je short L0033
    L0024: mov rcx, 0x7ff9125bbc18
    L002e: cmp [rax], rcx
    L0031: jne short L0038
    L0033: test rdx, rdx
    L0036: jne short L0058
    L0038: test rdx, rdx
    L003b: je short L004e
    L003d: mov rax, 0x7ff9125bc5d8
    L0047: cmp [rdx], rax
    L004a: je short L004e
    L004c: xor edx, edx
    L004e: test rdx, rdx
    L0051: setne al
    L0054: movzx eax, al
    L0057: ret
    L0058: mov eax, 1
    L005d: ret

C.M1(System.Object)
    L0000: test rdx, rdx
    L0003: je short L004a
    L0005: mov rax, rdx
    L0008: mov rcx, 0x7ff9125bb258
    L0012: cmp [rax], rcx
    L0015: je short L0044
    L0017: mov rax, rdx
    L001a: mov rcx, 0x7ff9125bbc18
    L0024: cmp [rax], rcx
    L0027: je short L0044
    L0029: mov rax, 0x7ff9125bc5d8
    L0033: cmp [rdx], rax
    L0036: je short L003a
    L0038: xor edx, edx
    L003a: test rdx, rdx
    L003d: setne al
    L0040: movzx eax, al
    L0043: ret
    L0044: mov eax, 1
    L0049: ret
    L004a: xor eax, eax
    L004c: ret

C.M2(System.Object)
    L0000: test rdx, rdx
    L0003: je short L003d
    L0005: mov rax, 0x7ff9125bb258
    L000f: cmp [rdx], rax
    L0012: je short L0037
    L0014: mov rax, 0x7ff9125bbc18
    L001e: cmp [rdx], rax
    L0021: je short L0037
    L0023: mov rax, 0x7ff9125bc5d8
    L002d: cmp [rdx], rax
    L0030: sete al
    L0033: movzx eax, al
    L0036: ret
    L0037: mov eax, 1
    L003c: ret
    L003d: xor eax, eax
    L003f: ret

sharplab

Looking through it:
The pattern matching codegen moves from rdx to rax for every test.
If there's no null check, JIT will generate null test for each condition.

The compiled IL of pattern matching uses ldnull and cgt for the last test. This pattern may confuse the JIT.

Configuration

sharplab default x64 (5.0 timeframe)

Regression?

Probably not.

category:cq
theme:redundant-branches
skill-level:expert
cost:medium
impact:small

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

Type

No type

Projects

Status

Optimizations

Relationships

None yet

Development

No branches or pull requests

Issue actions