From 4abc03747f0c3effb8b5ccc352c11909cfc837b5 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 4 Mar 2020 18:41:56 +0000 Subject: [PATCH] [mono] Optimize boxing in Pattern Matching idioms 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 https://github.com/dotnet/runtime/pull/1817) #### 1) `box + isinst + brtrue/brfalse` --> `ldc.i4.0/1 + brtrue/brfalse` ```csharp public static int Case1(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) { 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 o) { if (o is int x) return x; return 0; } // or public static int Case3_2(T o) => o is int n ? n : 42; // or public static int Case3_3(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 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 ``` --- mono/mini/method-to-ir.c | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/mono/mini/method-to-ir.c b/mono/mini/method-to-ir.c index 350ce3e11aed..aef7f438238d 100644 --- a/mono/mini/method-to-ir.c +++ b/mono/mini/method-to-ir.c @@ -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;