Skip to content

Commit

Permalink
[mono] Optimize boxing in Pattern Matching idioms
Browse files Browse the repository at this point in the history
This PR optimizes boxing for pattern matching idioms. Basically, it copies existing optimizations from CoreCLR to mono, see [impBoxPatternMatch](https://github.com/dotnet/runtime/blob/084dd1a5aae0fbe6ed4f9164ac4ee1c5f0b2398f/src/coreclr/src/jit/importer.cpp#L5856-L6032) (one of them was recently added by me for coreclr, see dotnet/runtime#1817)
#### 1) `box + isinst + brtrue/brfalse`  -->  `ldc.i4.0/1 + brtrue/brfalse`
```csharp
public static int Case1<T>(T t)
{
    if (t is int)
        return 42;
    return 0;
}
/*
    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: isinst [System.Private.CoreLib]System.Int32
    IL_000b: brfalse.s IL_0010
    IL_000d: ldc.i4.s 42
    IL_000f: ret
    IL_0010: ldc.i4.0
    IL_0011: ret
*/
```
#### 2) `box + isinst + ldnull + ceq/cgt.un`  -->  `ldc.i4.0/1`
```csharp
public static bool Case2<T>(T t)
{
    return t is int;
}
/*
    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: isinst [System.Private.CoreLib]System.Int32
    IL_000b: ldnull
    IL_000c: cgt.un
    IL_000e: ret
*/
```
#### 3) `box + isinst + unbox.any`  -->  `nop`
```csharp
public static int Case3_1<T>(T o)
{
    if (o is int x)
        return x;
    return 0;
}

// or

public static int Case3_2<T>(T o) => o is int n ? n : 42;

// or

public static int Case3_3<T>(T o)
{
    return o switch
    {
        int n => n,
        string str => str.Length,
        _ => 0
    };
}
```
Tests can be found here: https://github.com/dotnet/runtime/tree/master/src/coreclr/tests/src/JIT/Generics/Conversions/Boxing

[Sharplab.io link](https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQxASwDYB8CwAoAAQAYACAZQAsIEAHAGQmADoAlAVwDsY0w4BufPgIBmEgQCMANjEAmEgGF8Ab3wk1Y0ROlouC6HHEAeACoA+ABTGSMAJSr1KvOuck0AMxLmYrqK652nFyCCAHYSABYZAUDgsKJo5wBfezUUjTEpEmAAe2yMPSg4GRMLK1s0xyC1UOsfPxgE9WSYkjSRDO1deX1hAH0jM0sSbIDnSqr3T2y6nW8AD1Gq5xq5xtiSeLTmts1M2YK4PuLBqxGSAF5TYZndDhIAfhI7kAiowRb2rXqDvuESoZGFTSyzC0ygAHc0DAAMYUYFVcZLIL7O6XJ5IeFIjKkWAIC5XXFMOhwDgAcxgFAxLSxal6+I2mJciTWJGaznwiSAA===) to observe IL code for the samples above.

#### Codegen diff example:
```csharp
static bool Test(int n) => Validate(n);

static bool Validate<T>(T t) => t is int;
```
Before (for `Test`):
```asm
0000000000000000	pushq	%r14
0000000000000002	pushq	%rbx
0000000000000003	pushq	%rax
0000000000000004	movl	%edi, %ebx
0000000000000006	movabsq	$0x7fdf9c5090c0, %rax
0000000000000010	movabsq	$0x7fdfad0268e8, %r14
000000000000001a	leaq	0x1be800(%r14), %rdi
0000000000000021	movl	$0x14, %esi
0000000000000026	callq	*(%rax)
0000000000000028	movl	%ebx, 0x10(%rax)
000000000000002b	movq	(%rax), %rax
000000000000002e	cmpq	%r14, (%rax)
0000000000000031	sete	%al
0000000000000034	addq	$0x8, %rsp
0000000000000038	popq	%rbx
0000000000000039	popq	%r14
000000000000003b	retq
```
After:
```asm
0000000000000000	movb	$0x1, %al
0000000000000002	retq
```
  • Loading branch information
EgorBo committed Mar 4, 2020
1 parent e1d3b7b commit 4abc037
Showing 1 changed file with 94 additions and 0 deletions.
94 changes: 94 additions & 0 deletions mono/mini/method-to-ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -9017,6 +9017,100 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
break;
}
}

guint32 isinst_tk = 0;
if ((ip = il_read_op_and_token (next_ip, end, CEE_ISINST, MONO_CEE_ISINST, &isinst_tk)) &&
ip_in_bb (cfg, cfg->cbb, ip)) {
MonoClass *isinst_class = mini_get_class (method, isinst_tk, generic_context);
if (!mono_class_is_nullable (klass) && !mono_class_is_nullable (isinst_class) &&
!mini_is_gsharedvt_variable_klass (klass) && !mini_is_gsharedvt_variable_klass (isinst_class) &&
!mono_class_is_open_constructed_type (m_class_get_byval_arg (klass)) &&
!mono_class_is_open_constructed_type (m_class_get_byval_arg (isinst_class))) {

// Optimize
//
// box
// isinst [Type]
// brfalse/brtrue
//
// to
//
// ldc.i4.0 (or 1)
// brfalse/brtrue
//
guchar* br_ip = NULL;
if ((br_ip = il_read_brtrue (ip, end, &target)) || (br_ip = il_read_brtrue_s (ip, end, &target)) ||
(br_ip = il_read_brfalse (ip, end, &target)) || (br_ip = il_read_brfalse_s (ip, end, &target))) {

gboolean isinst = mono_class_is_assignable_from_internal (isinst_class, klass);
next_ip = ip;
il_op = (MonoOpcodeEnum) (isinst ? CEE_LDC_I4_1 : CEE_LDC_I4_0);
EMIT_NEW_ICONST (cfg, ins, isinst ? 1 : 0);
ins->type = STACK_I4;
*sp++ = ins;
break;
}

// Optimize
//
// box
// isinst [Type]
// ldnull
// ceq/cgt.un
//
// to
//
// ldc.i4.0 (or 1)
//
guchar* ldnull_ip = NULL;
if ((ldnull_ip = il_read_op (ip, end, CEE_LDNULL, MONO_CEE_LDNULL)) && ip_in_bb (cfg, cfg->cbb, ldnull_ip)) {
gboolean is_eq = FALSE, is_neq = FALSE;
if ((ip = il_read_op (ldnull_ip, end, CEE_PREFIX1, MONO_CEE_CEQ)))
is_eq = TRUE;
else if ((ip = il_read_op (ldnull_ip, end, CEE_PREFIX1, MONO_CEE_CGT_UN)))
is_neq = TRUE;

if ((is_eq || is_neq) && ip_in_bb (cfg, cfg->cbb, ip) &&
!mono_class_is_nullable (klass) && !mini_is_gsharedvt_klass (klass)) {
gboolean isinst = mono_class_is_assignable_from_internal (isinst_class, klass);
next_ip = ip;
if (is_eq)
isinst = !isinst;
il_op = (MonoOpcodeEnum) (isinst ? CEE_LDC_I4_1 : CEE_LDC_I4_0);
EMIT_NEW_ICONST (cfg, ins, isinst ? 1 : 0);
ins->type = STACK_I4;
*sp++ = ins;
break;
}
}

// Optimize
//
// box
// isinst [Type]
// unbox.any
//
// to
//
// nop
//
guchar* unbox_ip = NULL;
guint32 unbox_token = 0;
if ((unbox_ip = il_read_unbox_any (ip, end, &unbox_token)) && ip_in_bb (cfg, cfg->cbb, unbox_ip)) {
MonoClass *unbox_klass = mini_get_class (method, unbox_token, generic_context);
CHECK_TYPELOAD (unbox_klass);
if (!mono_class_is_nullable (unbox_klass) &&
!mini_is_gsharedvt_klass (unbox_klass) &&
klass == isinst_class &&
klass == unbox_klass)
{
*sp++ = val;
next_ip = unbox_ip;
break;
}
}
}
}
#endif

gboolean is_true;
Expand Down

0 comments on commit 4abc037

Please sign in to comment.